@@ -1643,12 +1643,18 @@ describe("runDiscover onResult hook (#6522)", () => {
16431643 } ) ;
16441644
16451645 describe ( "eligibility-exclusion signal tracking (#7982)" , ( ) => {
1646+ // #8544: `captured` keeps the full event (metadata included); `fired` stays the pre-#8544 projection so
1647+ // every existing assertion in this block is untouched.
1648+ const captured : Array < Record < string , unknown > > = [ ] ;
16461649 function fakeSignalStore ( ) {
16471650 const fired : Array < { ruleId : string ; targetKey : string ; outcome : string } > = [ ] ;
1651+ captured . length = 0 ;
16481652 return {
16491653 fired,
1654+ captured,
16501655 store : {
16511656 recordRuleFired : vi . fn ( async ( event : { ruleId : string ; targetKey : string ; outcome : string } ) => {
1657+ captured . push ( { ...event } ) ;
16521658 fired . push ( { ruleId : event . ruleId , targetKey : event . targetKey , outcome : event . outcome } ) ;
16531659 } ) ,
16541660 recordHumanOverride : vi . fn ( async ( ) => undefined ) ,
@@ -1674,6 +1680,82 @@ describe("runDiscover onResult hook (#6522)", () => {
16741680 ] ) ;
16751681 } ) ;
16761682
1683+ // #8544: bounded candidate-context metadata on the same fired events.
1684+ describe ( "bounded candidate context (#8544)" , ( ) => {
1685+ const runExcluded = async ( issueOverrides : Record < string , unknown > ) => {
1686+ const issues = [ fanOutIssue ( { issueNumber : 2 , ...issueOverrides } ) ] ;
1687+ const { opts } = discoverWith ( issues , new Map ( [ [ "acme/widgets" , trustworthyProfile ] ] ) ) ;
1688+ const { captured, store } = fakeSignalStore ( ) ;
1689+ vi . spyOn ( console , "log" ) . mockImplementation ( ( ) => undefined ) ;
1690+ await runDiscover ( [ "acme/widgets" , "--json" ] , { ...opts , initSignalTrackingStore : ( ) => store } ) ;
1691+ return captured [ 0 ] as { metadata ?: Record < string , unknown > } | undefined ;
1692+ } ;
1693+
1694+ it ( "captures labels, assignees and owner when all three are present" , async ( ) => {
1695+ const event = await runExcluded ( { labels : [ "blocked" ] , assignees : [ "octocat" ] , owner : "acme" } ) ;
1696+ expect ( event ?. metadata ) . toEqual ( { labels : [ "blocked" ] , assignees : [ "octocat" ] , owner : "acme" } ) ;
1697+ expect ( event ?. metadata ) . not . toHaveProperty ( "truncated" ) ;
1698+ } ) ;
1699+
1700+ it ( "omits each field that is absent from the candidate rather than storing null or []" , async ( ) => {
1701+ const event = await runExcluded ( { labels : [ "blocked" ] , assignees : undefined , owner : undefined } ) ;
1702+ expect ( event ?. metadata ) . toEqual ( { labels : [ "blocked" ] } ) ;
1703+ expect ( event ?. metadata ) . not . toHaveProperty ( "assignees" ) ;
1704+ expect ( event ?. metadata ) . not . toHaveProperty ( "owner" ) ;
1705+ } ) ;
1706+
1707+ it ( "keeps an empty array distinguishable from an absent field" , async ( ) => {
1708+ const event = await runExcluded ( { labels : [ "blocked" ] , assignees : [ ] } ) ;
1709+ expect ( ( event ?. metadata as { assignees ?: string [ ] } ) ?. assignees ) . toEqual ( [ ] ) ;
1710+ } ) ;
1711+
1712+ it ( "labels exactly at the 50 cap are kept whole with no truncated flag" , async ( ) => {
1713+ const labels = [ "blocked" , ...Array . from ( { length : 49 } , ( _ , i ) => `l${ i } ` ) ] ;
1714+ const event = await runExcluded ( { labels } ) ;
1715+ expect ( ( event ?. metadata as { labels : string [ ] } ) . labels ) . toHaveLength ( 50 ) ;
1716+ expect ( event ?. metadata ) . not . toHaveProperty ( "truncated" ) ;
1717+ } ) ;
1718+
1719+ it ( "labels one over the cap are clamped to 50 and flagged truncated" , async ( ) => {
1720+ const labels = [ "blocked" , ...Array . from ( { length : 50 } , ( _ , i ) => `l${ i } ` ) ] ;
1721+ const event = await runExcluded ( { labels } ) ;
1722+ expect ( ( event ?. metadata as { labels : string [ ] } ) . labels ) . toHaveLength ( 50 ) ;
1723+ expect ( event ?. metadata ) . toHaveProperty ( "truncated" , true ) ;
1724+ } ) ;
1725+
1726+ it ( "assignees exactly at the 25 cap are kept whole; one over is clamped and flagged" , async ( ) => {
1727+ const atCap = await runExcluded ( { labels : [ "blocked" ] , assignees : Array . from ( { length : 25 } , ( _ , i ) => `u${ i } ` ) } ) ;
1728+ expect ( ( atCap ?. metadata as { assignees : string [ ] } ) . assignees ) . toHaveLength ( 25 ) ;
1729+ expect ( atCap ?. metadata ) . not . toHaveProperty ( "truncated" ) ;
1730+
1731+ const overCap = await runExcluded ( { labels : [ "blocked" ] , assignees : Array . from ( { length : 26 } , ( _ , i ) => `u${ i } ` ) } ) ;
1732+ expect ( ( overCap ?. metadata as { assignees : string [ ] } ) . assignees ) . toHaveLength ( 25 ) ;
1733+ expect ( overCap ?. metadata ) . toHaveProperty ( "truncated" , true ) ;
1734+ } ) ;
1735+
1736+ it ( "a string exactly at the 200-char cap is kept whole; one char over is clamped and flagged" , async ( ) => {
1737+ const atCap = await runExcluded ( { labels : [ "blocked" , "x" . repeat ( 200 ) ] } ) ;
1738+ expect ( ( atCap ?. metadata as { labels : string [ ] } ) . labels [ 1 ] ) . toHaveLength ( 200 ) ;
1739+ expect ( atCap ?. metadata ) . not . toHaveProperty ( "truncated" ) ;
1740+
1741+ const overCap = await runExcluded ( { labels : [ "blocked" , "x" . repeat ( 201 ) ] } ) ;
1742+ expect ( ( overCap ?. metadata as { labels : string [ ] } ) . labels [ 1 ] ) . toHaveLength ( 200 ) ;
1743+ expect ( overCap ?. metadata ) . toHaveProperty ( "truncated" , true ) ;
1744+ } ) ;
1745+
1746+ it ( "omits metadata entirely when the candidate carries no context at all (pre-#8544 event shape)" , async ( ) => {
1747+ const event = await runExcluded ( { labels : undefined , assignees : undefined , owner : undefined } ) ;
1748+ expect ( event ) . toBeDefined ( ) ;
1749+ expect ( event ) . not . toHaveProperty ( "metadata" ) ;
1750+ } ) ;
1751+
1752+ it ( "clamps an over-long owner string and flags it" , async ( ) => {
1753+ const event = await runExcluded ( { labels : [ "blocked" ] , owner : "o" . repeat ( 201 ) } ) ;
1754+ expect ( ( event ?. metadata as { owner : string } ) . owner ) . toHaveLength ( 200 ) ;
1755+ expect ( event ?. metadata ) . toHaveProperty ( "truncated" , true ) ;
1756+ } ) ;
1757+ } ) ;
1758+
16771759 it ( "records nothing when nothing was excluded" , async ( ) => {
16781760 const issues = [ fanOutIssue ( { issueNumber : 1 , labels : [ "help wanted" ] } ) ] ;
16791761 const { opts } = discoverWith ( issues , new Map ( [ [ "acme/widgets" , trustworthyProfile ] ] ) ) ;
0 commit comments