@oscharko-dev/test-intelligence 0.0.1-beta.0 → 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../packages/security/src/secret-redaction.ts","../packages/security/src/logging.ts","../packages/security/src/semantic-content-sanitization.ts","../packages/contracts/src/index.ts","../packages/server/src/constants.ts","../packages/server/src/error-codes.ts","../packages/server/src/http-helpers.ts","../packages/server/src/observability.ts","../packages/server/src/rate-limit-store.ts","../packages/server/src/rate-limit.ts","../packages/server/src/request-security.ts","../packages/server/src/route-params.ts","../packages/server/src/test-intelligence-routes.ts","../packages/server/src/route-handlers.ts","../packages/server/src/request-handler.ts","../packages/server/src/package-identity.ts","../packages/server/src/openapi.ts","../packages/server/src/server.ts","../packages/server/src/server-entrypoint.ts"],"names":["createHash","timingSafeEqual"],"mappings":";;;;;;;;;AAKA,IAAM,sBAAA,GAAyB;AAC7B,EAAA,+EAAA;AACA,EAAA,2DAAA;AACA,EAAA,6DAAA;AACA,EAAA,qCAAA;AACA,EAAA,uFAAA;AACA,EAAA,uDAAA;AACA,EAAA;;AA4BF,IAAM,mBAAA,GAAsB;;;;AAI1B,EAAA,oEAAA;;AAEA,EAAA,iCAAA;;;;;AAKA,EAAA,6CAAA;;;AAGA,EAAA,kDAAA;;;;;AAKA,EAAA,gDAAA;;AAEA,EAAA;;AAGK,IAAM,qBAAA,GAAwB,CACnC,OAAA,EACA,WAAA,KACU;AAIV,EAAA,MAAM,OAAA,GAAU,sBAAA,CAAuB,MAAA,CAAO,CAAC,UAAU,OAAA,KAAW;AAClE,IAAA,OAAO,QAAA,CAAS,OAAA,CAAQ,OAAA,EAAS,CAAA,EAAA,EAAK,WAAW,CAAA,CAAE,CAAA;AACrD,EAAA,CAAA,EAAG,OAAO,CAAA;AAKV,EAAA,OAAO,mBAAA,CAAoB,MAAA,CAAO,CAAC,QAAA,EAAU,OAAA,KAAW;AACtD,IAAA,OAAO,QAAA,CAAS,OAAA,CAAQ,OAAA,EAAS,WAAW,CAAA;AAC9C,EAAA,CAAA,EAAG,OAAO,CAAA;AACZ,CAAA;;;ACzDO,IAAM,4BAAA,GAAmD,MAAA;AAChE,IAAM,2BAAA,GAAwD,MAAA;AACvD,IAAM,+BAAA,GACX,6BAAA;AACF,IAAM,oCAAA,GAGF;EACF,KAAA,EAAO,EAAA;EACP,IAAA,EAAM,EAAA;EACN,IAAA,EAAM,EAAA;EACN,KAAA,EAAO;;AAGT,IAAM,cAAA,GAAiB,CAAC,EACtB,KAAA,EACA,KAAA,EACA,OAAA,EACA,KAAA,EACA,KAAA,EACA,SAAA,EACA,KAAA,EACA,MAAA,EACA,IAAA,EACA,YAAU,KAYC;AACX,EAAA,MAAM,QAAA,GAAW,CAAC,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,CAAG,CAAA;AAC9B,EAAA,IAAI,UAAU,MAAA,EAAQ;AACpB,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,CAAG,CAAA;AAC5B,EAAA;AACA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA,KAAA,EAAQ,KAAK,CAAA,CAAA,CAAG,CAAA;AAChC,EAAA;AACA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA,OAAA,EAAU,KAAK,CAAA,CAAA,CAAG,CAAA;AAClC,EAAA;AACA,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA,SAAA,EAAY,SAAS,CAAA,CAAA,CAAG,CAAA;AACxC,EAAA;AACA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA,OAAA,EAAU,KAAK,CAAA,CAAA,CAAG,CAAA;AAClC,EAAA;AACA,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA,QAAA,EAAW,MAAM,CAAA,CAAA,CAAG,CAAA;AACpC,EAAA;AACA,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA,MAAA,EAAS,IAAI,CAAA,CAAA,CAAG,CAAA;AAChC,EAAA;AACA,EAAA,IAAI,eAAe,MAAA,EAAW;AAC5B,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA,QAAA,EAAW,UAAU,CAAA,CAAA,CAAG,CAAA;AACxC,EAAA;AACA,EAAA,OAAO,GAAG,QAAA,CAAS,IAAA,CAAK,EAAE,CAAC,IAAI,OAAO;;AACxC,CAAA;AAEO,IAAM,gBAAA,GAAmB,CAAC,OAAA,KAA2B;AAC1D,EAAA,OAAO,qBAAA,CAAsB,SAAS,YAAY,CAAA;AACpD,CAAA;AAmBO,IAAM,2BAA2B,CAAC,EACvC,KAAA,EACA,QAAA,GAAW,6BAA2B,KAIT;AAC7B,EAAA,MAAM,UAAA,GAAa,KAAA,EAAO,IAAA,EAAI,CAAG,WAAA,EAAW;AAC5C,EAAA,IACE,eAAe,OAAA,IACf,UAAA,KAAe,UACf,UAAA,KAAe,MAAA,IACf,eAAe,OAAA,EACf;AACA,IAAA,OAAO,UAAA;AACT,EAAA;AACA,EAAA,OAAO,QAAA;AACT,CAAA;AAEO,IAAM,qBAAA,GAAwB,CAAC,EACpC,MAAA,GAAS,8BACT,QAAA,EACA,GAAA,GAAM,MAAA,iBAAM,IAAI,MAAI,EAAG,WAAA,EAAW,EAClC,YAAA,GAAe,CAAC,IAAA,KAAgB;AAC9B,EAAA,OAAA,CAAQ,MAAA,CAAO,MAAM,IAAI,CAAA;AAC3B,CAAA,EACA,YAAA,GAAe,CAAC,IAAA,KAAgB;AAC9B,EAAA,OAAA,CAAQ,MAAA,CAAO,MAAM,IAAI,CAAA;AAC3B,CAAA,EACA,KAAA,GAAQ,mBAAA,EAAmB,GAQzB,EAAA,KAA8B;AAChC,EAAA,MAAM,mBAAmB,wBAAA,CAAyB;IAChD,KAAA,EAAO,QAAA,IAAY,OAAA,CAAQ,GAAA,CAAI,+BAA+B,CAAA;IAC9D,QAAA,EAAU;AACX,GAAA,CAAA;AAED,EAAA,OAAO;IACL,GAAA,EAAK,CAAC,EACJ,KAAA,EACA,OAAA,EACA,KAAA,EACA,KAAA,EACA,SAAA,EACA,KAAA,EACA,MAAA,EACA,IAAA,EACA,UAAA,EAAU,KACP;AACH,MAAA,IACE,oCAAA,CAAqC,KAAK,CAAA,GAC1C,oCAAA,CAAqC,gBAAgB,CAAA,EACrD;AACA,QAAA;AACF,MAAA;AAEA,MAAA,MAAM,gBAAA,GAAmB,iBAAiB,OAAO,CAAA;AACjD,MAAA,MAAM,IAAA,GACJ,MAAA,KAAW,MAAA,GACP,CAAA,EAAG,KAAK,SAAA,CAAU;AAChB,QAAA,EAAA,EAAI,GAAA,EAAG;AACP,QAAA,KAAA;QACA,GAAA,EAAK,gBAAA;AACL,QAAA,GAAI,KAAA,GAAQ,EAAE,KAAA,EAAK,GAAK,EAAA;AACxB,QAAA,GAAI,KAAA,GAAQ,EAAE,KAAA,EAAK,GAAK,EAAA;AACxB,QAAA,GAAI,SAAA,GAAY,EAAE,SAAA,EAAS,GAAK,EAAA;AAChC,QAAA,GAAI,KAAA,GAAQ,EAAE,KAAA,EAAK,GAAK,EAAA;AACxB,QAAA,GAAI,MAAA,GAAS,EAAE,MAAA,EAAM,GAAK,EAAA;AAC1B,QAAA,GAAI,IAAA,GAAO,EAAE,IAAA,EAAI,GAAK,EAAA;AACtB,QAAA,GAAI,UAAA,KAAe,MAAA,GAAY,EAAE,UAAA,KAAe;OACjD,CAAC;IACF,cAAA,CAAe;AACb,QAAA,KAAA;AACA,QAAA,KAAA;QACA,OAAA,EAAS,gBAAA;AACT,QAAA,GAAI,KAAA,GAAQ,EAAE,KAAA,EAAK,GAAK,EAAA;AACxB,QAAA,GAAI,KAAA,GAAQ,EAAE,KAAA,EAAK,GAAK,EAAA;AACxB,QAAA,GAAI,SAAA,GAAY,EAAE,SAAA,EAAS,GAAK,EAAA;AAChC,QAAA,GAAI,KAAA,GAAQ,EAAE,KAAA,EAAK,GAAK,EAAA;AACxB,QAAA,GAAI,MAAA,GAAS,EAAE,MAAA,EAAM,GAAK,EAAA;AAC1B,QAAA,GAAI,IAAA,GAAO,EAAE,IAAA,EAAI,GAAK,EAAA;AACtB,QAAA,GAAI,UAAA,KAAe,MAAA,GAAY,EAAE,UAAA,KAAe;AACjD,OAAA,CAAA;AAEP,MAAA,IAAI,KAAA,KAAU,MAAA,IAAU,KAAA,KAAU,OAAA,EAAS;AACzC,QAAA,YAAA,CAAa,IAAI,CAAA;AACjB,QAAA;AACF,MAAA;AACA,MAAA,YAAA,CAAa,IAAI,CAAA;AACnB,IAAA;;AAEJ,CAAA;;;ACxIO,IAAM,6BAAA,GAAgC;AAC3C,EAAA,sBAAA;AACA,EAAA,sBAAA;AACA,EAAA,gBAAA;AACA,EAAA,wBAAA;AACA,EAAA,qBAAA;AACA,EAAA,YAAA;AACA,EAAA,oBAAA;AACA,EAAA;;AA+aA,IAAI,GAAA,CAAI,6BAA6B;;;ACvFhC,IAAM,qBAAA,GAAwB,2BAAA;;;ACrZ9B,IAAM,YAAA,GAAuB,WAAA;AAG7B,IAAM,YAAA,GAAuB,IAAA;AAM7B,IAAM,6BAAA,GAAwC,EAAA;AAG9C,IAAM,oBAAA,GAA+B,GAAA;AAMrC,IAAM,sBAAA,GAAiC,OAAA;AAMvC,IAAM,qBAAA,GAAgC,OAAA;AAGtC,IAAM,eAAA,GAA0B,+BAAA;AAGhC,IAAM,iCAAA,GAA4C,kBAAA;AAOlD,IAAM,+BAAA,GACX,oGAAA;AAGK,IAAM,gBAAA,GAA2B,SAAA;AAMjC,IAAM,8BAAA,GAAiC,CAC5C,GAAA,GAAyB,OAAA,CAAQ,GAAA,KACrB;AACZ,EAAA,OAAO,eAAA,CAAgB,GAAA,CAAI,qBAAqB,CAAC,CAAA;AACnD,CAAA;AAmBO,IAAM,8BAAA,GAAiC,CAC5C,GAAA,GAAyB,OAAA,CAAQ,GAAA,KACV;AACvB,EAAA,MAAM,GAAA,GAAM,IAAI,eAAe,CAAA;AAC/B,EAAA,IAAI,QAAQ,MAAA,EAAW;AACrB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,MAAM,UAAA,GAAa,GAAA,CAAI,IAAA,EAAK,CAAE,WAAA,EAAY;AAC1C,EAAA,IACE,UAAA,KAAe,MACf,UAAA,KAAe,GAAA,IACf,eAAe,OAAA,IACf,UAAA,KAAe,IAAA,IACf,UAAA,KAAe,KAAA,EACf;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,OAAO,iCAAA;AACT,CAAA;AAEA,IAAM,eAAA,GAAkB,CAAC,GAAA,KAAqC;AAC5D,EAAA,IAAI,QAAQ,MAAA,EAAW;AACrB,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,MAAM,UAAA,GAAa,GAAA,CAAI,IAAA,EAAK,CAAE,WAAA,EAAY;AAC1C,EAAA,OACE,eAAe,GAAA,IACf,UAAA,KAAe,MAAA,IACf,UAAA,KAAe,SACf,UAAA,KAAe,IAAA;AAEnB,CAAA;;;ACjFO,IAAM,kBAAA,GAAqB,CAAC,IAAA,KAA4C;AAC7E,EAAA,QAAQ,IAAA;AAAM,IACZ,KAAK,aAAA;AACH,MAAA,OAAO,GAAA;AAAA,IACT,KAAK,cAAA;AAAA,IACL,KAAK,sBAAA;AACH,MAAA,OAAO,GAAA;AAAA,IACT,KAAK,0BAAA;AAAA,IACL,KAAK,uBAAA;AACH,MAAA,OAAO,GAAA;AAAA,IACT,KAAK,WAAA;AACH,MAAA,OAAO,GAAA;AAAA,IACT,KAAK,oBAAA;AACH,MAAA,OAAO,GAAA;AAAA,IACT,KAAK,mBAAA;AACH,MAAA,OAAO,GAAA;AAAA,IACT,KAAK,wBAAA;AACH,MAAA,OAAO,GAAA;AAAA,IACT,KAAK,cAAA;AACH,MAAA,OAAO,GAAA;AAAA,IACT,KAAK,gBAAA;AAAA,IACL,KAAK,qBAAA;AACH,MAAA,OAAO,GAAA;AAAA,IACT,KAAK,4BAAA;AAAA,IACL,KAAK,0BAAA;AAAA,IACL,KAAK,oBAAA;AACH,MAAA,OAAO,GAAA;AAAA;AAEb,CAAA;;;ACnCO,IAAM,oBAAA,GAAuB,CAClC,QAAA,EACA,OAAA,GAAiC,EAAC,KACzB;AACT,EAAA,QAAA,CAAS,SAAA,CAAU,0BAA0B,SAAS,CAAA;AACtD,EAAA,QAAA,CAAS,SAAA,CAAU,mBAAmB,aAAa,CAAA;AACnD,EAAA,QAAA,CAAS,SAAA;AAAA,IACP,yBAAA;AAAA,IACA,QAAQ,qBAAA,IAAyB;AAAA,GACnC;AACA,EAAA,MAAM,IAAA,GACJ,OAAA,CAAQ,uBAAA,IAA2B,8BAAA,EAA+B;AACpE,EAAA,IAAI,SAAS,MAAA,EAAW;AACtB,IAAA,QAAA,CAAS,SAAA,CAAU,6BAA6B,IAAI,CAAA;AAAA,EACtD;AACF,CAAA;AAEO,IAAM,oBAAoB,CAAC;AAAA,EAChC,QAAA;AAAA,EACA,UAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,KAKY;AACV,EAAA,oBAAA,CAAqB,QAAQ,CAAA;AAC7B,EAAA,QAAA,CAAS,SAAA,CAAU,gBAAgB,iCAAiC,CAAA;AACpE,EAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,IAAA,KAAA,MAAW,CAAC,IAAA,EAAM,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,YAAY,CAAA,EAAG;AACxD,MAAA,QAAA,CAAS,SAAA,CAAU,MAAM,KAAK,CAAA;AAAA,IAChC;AAAA,EACF;AACA,EAAA,QAAA,CAAS,UAAA,GAAa,UAAA;AACtB,EAAA,QAAA,CAAS,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AACtC,CAAA;AAEO,IAAM,qBAAqB,CAAC;AAAA,EACjC,QAAA;AAAA,EACA,IAAA;AAAA,EACA,OAAA;AAAA,EACA,YAAA;AAAA,EACA;AACF,CAAA,KAMY;AACV,EAAA,MAAM,QAAA,GAA0C,EAAE,KAAA,EAAO,IAAA,EAAM,OAAA,EAAQ;AACvE,EAAA,iBAAA,CAAkB;AAAA,IAChB,QAAA;AAAA,IACA,UAAA,EAAY,UAAA,IAAc,kBAAA,CAAmB,IAAI,CAAA;AAAA,IACjD,OAAA,EAAS,QAAA;AAAA,IACT,GAAI,YAAA,KAAiB,MAAA,GAAY,EAAE,YAAA,KAAiB;AAAC,GACtD,CAAA;AACH,CAAA;AAeO,IAAM,eAAe,OAAO;AAAA,EACjC,OAAA;AAAA,EACA;AACF,CAAA,KAGmC;AACjC,EAAA,MAAM,SAAmB,EAAC;AAC1B,EAAA,IAAI,QAAA,GAAW,CAAA;AAEf,EAAA,WAAA,MAAiB,SAAS,OAAA,EAAS;AACjC,IAAA,MAAM,GAAA,GACJ,OAAO,KAAA,KAAU,QAAA,GACb,OAAO,IAAA,CAAK,KAAA,EAAO,MAAM,CAAA,GACxB,KAAA;AACP,IAAA,QAAA,IAAY,GAAA,CAAI,MAAA;AAChB,IAAA,IAAI,WAAW,QAAA,EAAU;AACvB,MAAA,OAAO;AAAA,QACL,EAAA,EAAI,KAAA;AAAA,QACJ,IAAA,EAAM,mBAAA;AAAA,QACN,OAAA,EAAS,wBAAwB,QAAQ,CAAA,OAAA;AAAA,OAC3C;AAAA,IACF;AACA,IAAA,MAAA,CAAO,KAAK,GAAG,CAAA;AAAA,EACjB;AAEA,EAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AACvB,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,KAAA;AAAA,MACJ,IAAA,EAAM,aAAA;AAAA,MACN,OAAA,EAAS;AAAA,KACX;AAAA,EACF;AAEA,EAAA,MAAM,OAAO,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA,CAAE,SAAS,MAAM,CAAA;AAClD,EAAA,IAAI;AACF,IAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,OAAO,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA,EAAa;AAAA,EACxD,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,KAAA;AAAA,MACJ,IAAA,EAAM,aAAA;AAAA,MACN,OAAA,EAAS;AAAA,KACX;AAAA,EACF;AACF,CAAA;AAQO,IAAM,gBAAA,GAAmB,CAC9B,QAAA,EACA,OAAA,GAAiC,EAAC,KACpB;AACd,EAAA,oBAAA,CAAqB,UAAU,OAAO,CAAA;AACtC,EAAA,QAAA,CAAS,SAAA,CAAU,gBAAgB,kCAAkC,CAAA;AACrE,EAAA,QAAA,CAAS,SAAA,CAAU,iBAAiB,wBAAwB,CAAA;AAC5D,EAAA,QAAA,CAAS,SAAA,CAAU,cAAc,YAAY,CAAA;AAC7C,EAAA,QAAA,CAAS,UAAA,GAAa,GAAA;AACtB,EAAA,QAAA,CAAS,YAAA,EAAa;AAEtB,EAAA,OAAO;AAAA,IACL,UAAA,CAAW,EAAE,KAAA,EAAO,IAAA,EAAM,IAAG,EAAG;AAC9B,MAAA,MAAM,QAAkB,EAAC;AACzB,MAAA,IAAI,OAAO,MAAA,EAAW;AACpB,QAAA,KAAA,CAAM,IAAA,CAAK,CAAA,IAAA,EAAO,EAAE,CAAA,CAAE,CAAA;AAAA,MACxB;AACA,MAAA,KAAA,CAAM,IAAA,CAAK,CAAA,OAAA,EAAU,KAAK,CAAA,CAAE,CAAA;AAC5B,MAAA,MAAM,UAAA,GAAa,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA;AAKtC,MAAA,KAAA,MAAW,IAAA,IAAQ,UAAA,CAAW,KAAA,CAAM,IAAI,CAAA,EAAG;AACzC,QAAA,KAAA,CAAM,IAAA,CAAK,CAAA,MAAA,EAAS,IAAI,CAAA,CAAE,CAAA;AAAA,MAC5B;AACA,MAAA,KAAA,CAAM,IAAA,CAAK,IAAI,EAAE,CAAA;AACjB,MAAA,QAAA,CAAS,KAAA,CAAM,KAAA,CAAM,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,IACjC,CAAA;AAAA,IACA,WAAW,OAAA,EAAS;AAClB,MAAA,QAAA,CAAS,KAAA,CAAM,CAAA,OAAA,EAAU,MAAA,CAAO,OAAO,CAAC;;AAAA,CAAM,CAAA;AAAA,IAChD,CAAA;AAAA,IACA,GAAA,GAAM;AACJ,MAAA,QAAA,CAAS,GAAA,EAAI;AAAA,IACf;AAAA,GACF;AACF,CAAA;AAEA,IAAM,oBAAA,GAAuB,iBAAA;AAOtB,IAAM,gBAAA,GAAmB,CAAC,OAAA,KAAqC;AACpE,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,OAAA,CAAQ,oBAAoB,CAAA;AACtD,EAAA,MAAM,MAAM,KAAA,CAAM,OAAA,CAAQ,SAAS,CAAA,GAAI,SAAA,CAAU,CAAC,CAAA,GAAI,SAAA;AACtD,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,SAAS,CAAA,EAAG;AAC7C,IAAA,MAAM,QAAQ,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,GAAG,IAAA,EAAK;AACtC,IAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG;AAC3C,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,OAAO,OAAA,CAAQ,OAAO,aAAA,IAAiB,SAAA;AACzC,CAAA;AC/KO,IAAM,mBAAA,GAAsB,CACjC,MAAA,KACkB;AAClB,EAAA,OAAO;AAAA,IACL,GAAA,CAAI,EAAE,SAAA,EAAW,MAAA,EAAQ,MAAM,UAAA,EAAY,UAAA,EAAY,SAAA,EAAW,KAAA,EAAM,EAAG;AACzE,MAAA,MAAM,QAAA,GAAW;AAAA,QACf,UAAU,MAAM,CAAA,CAAA;AAAA,QAChB,QAAQ,IAAI,CAAA,CAAA;AAAA,QACZ,CAAA,OAAA,EAAU,MAAA,CAAO,UAAU,CAAC,CAAA,CAAA;AAAA,QAC5B,CAAA,WAAA,EAAc,MAAA,CAAO,UAAU,CAAC,CAAA,CAAA;AAAA,QAChC,UAAU,SAAS,CAAA;AAAA,OACrB;AACA,MAAA,IAAI,UAAU,MAAA,EAAW;AACvB,QAAA,QAAA,CAAS,IAAA,CAAK,CAAA,MAAA,EAAS,KAAK,CAAA,CAAE,CAAA;AAAA,MAChC;AACA,MAAA,MAAA,CAAO,GAAA,CAAI;AAAA,QACT,KAAA,EAAO,UAAA,IAAc,GAAA,GAAM,OAAA,GAAU,MAAA;AAAA,QACrC,OAAA,EAAS,QAAA,CAAS,IAAA,CAAK,GAAG,CAAA;AAAA,QAC1B,SAAA;AAAA,QACA,MAAA;AAAA,QACA,IAAA;AAAA,QACA;AAAA,OACD,CAAA;AAAA,IACH,CAAA;AAAA,IACA,YAAA,GAAe;AACb,MAAA,OAAO,UAAA,EAAW;AAAA,IACpB;AAAA,GACF;AACF,CAAA;AAoBO,IAAM,oBAAoB,CAAC;AAAA,EAChC,KAAA;AAAA,EACA,GAAA,GAAM,MAAA,iBAAM,IAAI,IAAA,IAAO,WAAA;AACzB,CAAA,KAA2C;AACzC,EAAA,OAAO;AAAA,IACL,OAAO,KAAA,EAAO;AACZ,MAAA,MAAM,IAAA,GAAO,KAAK,SAAA,CAAU;AAAA,QAC1B,IAAI,GAAA,EAAI;AAAA,QACR,QAAQ,KAAA,CAAM,MAAA;AAAA,QACd,SAAS,KAAA,CAAM,OAAA;AAAA,QACf,SAAS,KAAA,CAAM,OAAA;AAAA,QACf,GAAI,MAAM,SAAA,KAAc,MAAA,GACpB,EAAE,SAAA,EAAW,KAAA,CAAM,SAAA,EAAU,GAC7B,EAAC;AAAA,QACL,GAAI,MAAM,SAAA,KAAc,MAAA,GACpB,EAAE,SAAA,EAAW,KAAA,CAAM,SAAA,EAAU,GAC7B,EAAC;AAAA,QACL,GAAI,MAAM,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,EAAS,KAAA,CAAM,OAAA,EAAQ,GAAI;AAAC,OACjE,CAAA;AACD,MAAA,KAAA,CAAM,GAAG,IAAI;AAAA,CAAI,CAAA;AAAA,IACnB;AAAA,GACF;AACF,CAAA;;;AC5FO,IAAM,iBAAN,MAAqB;AAAA,EACT,OAAA,uBAA4C,GAAA,EAAI;AAAA,EAEjE,IAAI,GAAA,EAA0C;AAC5C,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA;AAAA,EAC7B;AAAA,EAEA,GAAA,CAAI,KAAa,MAAA,EAA+B;AAC9C,IAAA,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,GAAA,EAAK,MAAM,CAAA;AAAA,EAC9B;AAAA,EAEA,OAAO,GAAA,EAAmB;AACxB,IAAA,IAAA,CAAK,OAAA,CAAQ,OAAO,GAAG,CAAA;AAAA,EACzB;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,QAAQ,KAAA,EAAM;AAAA,EACrB;AAAA,EAEA,IAAA,GAAe;AACb,IAAA,OAAO,KAAK,OAAA,CAAQ,IAAA;AAAA,EACtB;AACF,CAAA;;;ACaO,IAAM,oBAAoB,CAAC;AAAA,EAChC,iBAAA;AAAA,EACA,KAAA,GAAQ,IAAI,cAAA;AACd,CAAA,KAA2C;AACzC,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,iBAAiB,CAAA,IAAK,oBAAoB,CAAA,EAAG;AAChE,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,iBAAiB,CAAA;AAE1C,EAAA,OAAO;AAAA,IACL,KAAA,CAAM,EAAE,SAAA,EAAW,QAAA,EAAU,OAAM,EAAG;AACpC,MAAA,MAAM,GAAA,GAAM,CAAA,EAAG,SAAS,CAAA,EAAA,EAAO,QAAQ,CAAA,CAAA;AACvC,MAAA,MAAM,QAAA,GAAW,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAC9B,MAAA,IACE,QAAA,KAAa,MAAA,IACb,KAAA,GAAQ,QAAA,CAAS,qBAAqB,oBAAA,EACtC;AACA,QAAA,KAAA,CAAM,IAAI,GAAA,EAAK,EAAE,OAAO,CAAA,EAAG,iBAAA,EAAmB,OAAO,CAAA;AACrD,QAAA,OAAO;AAAA,UACL,EAAA,EAAI,IAAA;AAAA,UACJ,WAAW,KAAA,GAAQ,CAAA;AAAA,UACnB,WAAW,KAAA,GAAQ;AAAA,SACrB;AAAA,MACF;AAEA,MAAA,IAAI,QAAA,CAAS,SAAS,KAAA,EAAO;AAC3B,QAAA,MAAM,SAAA,GAAY,SAAS,iBAAA,GAAoB,oBAAA;AAC/C,QAAA,OAAO;AAAA,UACL,EAAA,EAAI,KAAA;AAAA,UACJ,iBAAA,EAAmB,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,IAAA,CAAA,CAAM,SAAA,GAAY,KAAA,IAAS,GAAI,CAAC,CAAA;AAAA,UACpE;AAAA,SACF;AAAA,MACF;AAEA,MAAA,QAAA,CAAS,KAAA,IAAS,CAAA;AAClB,MAAA,OAAO;AAAA,QACL,EAAA,EAAI,IAAA;AAAA,QACJ,WAAW,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAA,GAAQ,SAAS,KAAK,CAAA;AAAA,QAC7C,SAAA,EAAW,SAAS,iBAAA,GAAoB;AAAA,OAC1C;AAAA,IACF,CAAA;AAAA,IACA,KAAA,GAAQ;AACN,MAAA,KAAA,CAAM,KAAA,EAAM;AAAA,IACd;AAAA,GACF;AACF,CAAA;AC/EA,IAAM,yBAAA,GAA4B,mCAAA;AAClC,IAAM,gDAAgC,IAAI,GAAA,CAAI,CAAC,aAAA,EAAe,WAAW,CAAC,CAAA;AAC1E,IAAM,8BAAA,GAAiC,mBAAA;AAkCvC,IAAM,cAAA,GAAiB,CACrB,KAAA,KACuB;AACvB,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,IAAA,OAAO,MAAM,CAAC,CAAA;AAAA,EAChB;AACA,EAAA,OAAO,MAAA;AACT,CAAA;AAEA,IAAM,eAAA,GAAkB,CAAC,KAAA,KAAkD;AACzE,EAAA,IAAI,UAAU,MAAA,IAAa,KAAA,CAAM,IAAA,EAAK,CAAE,WAAW,CAAA,EAAG;AACpD,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,GAAA,CAAI,KAAK,CAAA,CAAE,MAAA;AAAA,EACxB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF,CAAA;AAEA,IAAM,mBAAA,GAAsB,CAAC,IAAA,KAAyB;AACpD,EAAA,IAAI,IAAA,CAAK,SAAS,GAAG,CAAA,IAAK,CAAC,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG;AAC/C,IAAA,OAAO,IAAI,IAAI,CAAA,CAAA,CAAA;AAAA,EACjB;AACA,EAAA,OAAO,IAAA;AACT,CAAA;AAEA,IAAM,kBAAA,GAAqB,CAAC,IAAA,KAA0B;AACpD,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,IAAA,EAAK,CAAE,WAAA,EAAY;AAC3C,EAAA,OACE,UAAA,KAAe,WAAA,IACf,UAAA,KAAe,WAAA,IACf,UAAA,KAAe,KAAA,IACf,UAAA,KAAe,OAAA,IACf,UAAA,KAAe,SAAA,IACf,UAAA,KAAe,IAAA,IACf,UAAA,KAAe,MAAA;AAEnB,CAAA;AAEO,IAAM,yBAAyB,CAAC;AAAA,EACrC,IAAA;AAAA,EACA;AACF,CAAA,KAGmB;AACjB,EAAA,MAAM,OAAA,uBAAc,GAAA,CAAY;AAAA,IAC9B,CAAA,OAAA,EAAU,mBAAA,CAAoB,IAAI,CAAC,IAAI,IAAI,CAAA;AAAA,GAC5C,CAAA;AACD,EAAA,IAAI,kBAAA,CAAmB,IAAI,CAAA,EAAG;AAC5B,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,iBAAA,EAAoB,IAAI,CAAA,CAAE,CAAA;AACtC,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,iBAAA,EAAoB,IAAI,CAAA,CAAE,CAAA;AACtC,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,aAAA,EAAgB,IAAI,CAAA,CAAE,CAAA;AAAA,EACpC;AACA,EAAA,OAAO,OAAA;AACT,CAAA;AAEO,IAAM,uBAAuB,CAAC;AAAA,EACnC,OAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,KAI6B;AAC3B,EAAA,MAAM,WAAA,GAAc,cAAA,CAAe,OAAA,CAAQ,OAAA,CAAQ,cAAc,CAAC,CAAA;AAClE,EAAA,IACE,gBAAgB,MAAA,IAChB,CAAC,yBAAA,CAA0B,IAAA,CAAK,WAAW,CAAA,EAC3C;AACA,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,KAAA;AAAA,MACJ,UAAA,EAAY,GAAA;AAAA,MACZ,OAAA,EAAS;AAAA,QACP,KAAA,EAAO,wBAAA;AAAA,QACP,OAAA,EAAS;AAAA;AACX,KACF;AAAA,EACF;AAEA,EAAA,MAAM,YAAA,GAAe,cAAA,CAAe,OAAA,CAAQ,OAAA,CAAQ,MAAM,CAAA;AAC1D,EAAA,MAAM,aAAA,GAAgB,cAAA,CAAe,OAAA,CAAQ,OAAA,CAAQ,OAAO,CAAA;AAC5D,EAAA,MAAM,YAAA,GAAe,eAAe,OAAA,CAAQ,OAAA,CAAQ,gBAAgB,CAAC,CAAA,EACjE,IAAA,EAAK,CACN,WAAA,EAAY;AACf,EAAA,MAAM,cAAA,GAAiB,sBAAA,CAAuB,EAAE,IAAA,EAAM,MAAM,CAAA;AAE5D,EAAA,IACE,iBAAiB,MAAA,IACjB,CAAC,6BAAA,CAA8B,GAAA,CAAI,YAAY,CAAA,EAC/C;AACA,IAAA,OAAO,eAAA;AAAA,MACL;AAAA,KACF;AAAA,EACF;AAEA,EAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,IAAA,MAAM,MAAA,GAAS,gBAAgB,YAAY,CAAA;AAC3C,IAAA,IAAI,WAAW,MAAA,IAAa,CAAC,cAAA,CAAe,GAAA,CAAI,MAAM,CAAA,EAAG;AACvD,MAAA,OAAO,eAAA;AAAA,QACL;AAAA,OACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IAAI,kBAAkB,MAAA,EAAW;AAC/B,IAAA,MAAM,aAAA,GAAgB,gBAAgB,aAAa,CAAA;AACnD,IAAA,IAAI,kBAAkB,MAAA,IAAa,CAAC,cAAA,CAAe,GAAA,CAAI,aAAa,CAAA,EAAG;AACrE,MAAA,OAAO,eAAA;AAAA,QACL;AAAA,OACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,IAAI,IAAA,EAAK;AACpB,CAAA;AAEA,IAAM,eAAA,GAAkB,CAAC,OAAA,MAAyC;AAAA,EAChE,EAAA,EAAI,KAAA;AAAA,EACJ,UAAA,EAAY,GAAA;AAAA,EACZ,OAAA,EAAS,EAAE,KAAA,EAAO,0BAAA,EAA4B,OAAA;AAChD,CAAA,CAAA;AAEA,IAAM,8BAAA,GAAiC,CACrC,KAAA,KACuB;AACvB,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,MAAM,OAAA,GAAU,MAAM,IAAA,EAAK;AAC3B,EAAA,OAAO,OAAA,CAAQ,MAAA,GAAS,CAAA,GAAI,OAAA,GAAU,MAAA;AACxC,CAAA;AAEO,IAAM,eAAA,GAAkB,CAC7B,OAAA,KACuB;AACvB,EAAA,MAAM,aAAA,GAAgB,cAAA,CAAe,OAAA,CAAQ,OAAA,CAAQ,aAAa,CAAA;AAClE,EAAA,IAAI,kBAAkB,MAAA,EAAW;AAC/B,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,cAAA,GAAiB,QAAA;AACvB,EAAA,IAAI,aAAA,CAAc,MAAA,IAAU,cAAA,CAAe,MAAA,EAAQ;AACjD,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,KAAA,IAAS,QAAQ,CAAA,EAAG,KAAA,GAAQ,cAAA,CAAe,MAAA,EAAQ,SAAS,CAAA,EAAG;AAC7D,IAAA,MAAM,IAAA,GAAO,aAAA,CAAc,UAAA,CAAW,KAAK,CAAA;AAC3C,IAAA,MAAM,OAAA,GACJ,IAAA,IAAQ,EAAA,IAAQ,IAAA,IAAQ,EAAA,GACpB,MAAA,CAAO,YAAA,CAAa,IAAA,GAAO,EAAI,CAAA,GAC/B,aAAA,CAAc,KAAK,CAAA;AACzB,IAAA,IAAI,OAAA,KAAY,cAAA,CAAe,KAAK,CAAA,EAAG;AACrC,MAAA,OAAO,MAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,IAAI,aAAa,cAAA,CAAe,MAAA;AAChC,EAAA,OAAO,UAAA,GAAa,cAAc,MAAA,EAAQ;AACxC,IAAA,MAAM,IAAA,GAAO,aAAA,CAAc,UAAA,CAAW,UAAU,CAAA;AAChD,IAAA,IAAI,IAAA,KAAS,EAAA,IAAQ,IAAA,KAAS,CAAA,EAAM;AAClC,MAAA;AAAA,IACF;AACA,IAAA,UAAA,IAAc,CAAA;AAAA,EAChB;AAEA,EAAA,IACE,UAAA,KAAe,cAAA,CAAe,MAAA,IAC9B,UAAA,IAAc,cAAc,MAAA,EAC5B;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,IAAI,WAAW,aAAA,CAAc,MAAA;AAC7B,EAAA,OAAO,WAAW,UAAA,EAAY;AAC5B,IAAA,MAAM,IAAA,GAAO,aAAA,CAAc,UAAA,CAAW,QAAA,GAAW,CAAC,CAAA;AAClD,IAAA,IAAI,IAAA,KAAS,EAAA,IAAQ,IAAA,KAAS,CAAA,EAAM;AAClC,MAAA;AAAA,IACF;AACA,IAAA,QAAA,IAAY,CAAA;AAAA,EACd;AAEA,EAAA,OAAO,WAAW,UAAA,GACd,aAAA,CAAc,KAAA,CAAM,UAAA,EAAY,QAAQ,CAAA,GACxC,MAAA;AACN,CAAA;AAEA,IAAM,WAAA,GAAc,CAAC,QAAA,EAAkB,SAAA,KAA+B;AACpE,EAAA,MAAM,cAAA,GAAiBA,WAAW,QAAQ,CAAA,CAAE,OAAO,QAAA,EAAU,MAAM,EAAE,MAAA,EAAO;AAC5E,EAAA,MAAM,eAAA,GAAkBA,WAAW,QAAQ,CAAA,CACxC,OAAO,SAAA,EAAW,MAAM,EACxB,MAAA,EAAO;AACV,EAAA,OAAOC,eAAAA,CAAgB,gBAAgB,eAAe,CAAA;AACxD,CAAA;AAEO,IAAM,sBAAsB,CAAC;AAAA,EAClC,OAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA,KAIwB;AACtB,EAAA,MAAM,eAAA,GAAkB,+BAA+B,WAAW,CAAA;AAClE,EAAA,IAAI,oBAAoB,MAAA,EAAW;AACjC,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,KAAA;AAAA,MACJ,UAAA,EAAY,GAAA;AAAA,MACZ,OAAA,EAAS;AAAA,QACP,KAAA,EAAO,4BAAA;AAAA,QACP,OAAA,EAAS,GAAG,UAAU,CAAA,sEAAA;AAAA;AACxB,KACF;AAAA,EACF;AAEA,EAAA,MAAM,aAAA,GAAgB,gBAAgB,OAAO,CAAA;AAC7C,EAAA,IACE,aAAA,KAAkB,MAAA,IAClB,WAAA,CAAY,eAAA,EAAiB,aAAa,CAAA,EAC1C;AACA,IAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,WAAW,EAAE,MAAA,EAAQ,UAAS,EAAE;AAAA,EACrD;AAEA,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,KAAA;AAAA,IACJ,UAAA,EAAY,GAAA;AAAA,IACZ,OAAA,EAAS;AAAA,MACP,KAAA,EAAO,cAAA;AAAA,MACP,OAAA,EAAS,GAAG,UAAU,CAAA,qCAAA;AAAA,KACxB;AAAA,IACA,eAAA,EAAiB,iBAAiB,8BAA8B,CAAA,CAAA;AAAA,GAClE;AACF,CAAA;ACrOA,IAAM,iBAAA,GAAoB,0BAAA;AAEnB,IAAM,eAAA,GAAkB,CAAC,OAAA,KAA6B;AAC3D,EAAA,IAAI,CAAC,iBAAA,CAAkB,IAAA,CAAK,OAAO,CAAA,EAAG;AACpC,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,IAAI,OAAA,KAAY,GAAA,IAAO,OAAA,KAAY,IAAA,EAAM;AACvC,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAA;AACT,CAAA;AAOO,IAAM,kBAAA,GAAqB,CAAC,QAAA,KAA6B;AAC9D,EAAA,IAAI,SAAS,MAAA,GAAS,CAAA,IAAK,QAAA,CAAS,QAAA,CAAS,GAAG,CAAA,EAAG;AACjD,IAAA,OAAO,QAAA,CAAS,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AAAA,EAC7B;AACA,EAAA,OAAO,QAAA;AACT,CAAA;;;ACvBO,IAAM,6BAA6B,CAAC;AAAA,EACzC,MAAA;AAAA,EACA;AACF,CAAA,KAGwB;AACtB,EAAA,MAAM,KAAA,GAAQ,OAAO,WAAA,EAAY;AACjC,EAAA,IAAI,UAAU,SAAA,EAAW;AACvB,IAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,OAAO,EAAE,IAAA,EAAM,kBAAiB,EAAE;AAAA,EACvD;AAEA,EAAA,MAAM,UAAA,GAAa,mBAAmB,QAAQ,CAAA;AAE9C,EAAA,IAAI,eAAe,UAAA,EAAY;AAC7B,IAAA,OAAO,YAAY,KAAA,EAAO,KAAA,EAAO,EAAE,IAAA,EAAM,WAAW,CAAA;AAAA,EACtD;AACA,EAAA,IAAI,eAAe,SAAA,EAAW;AAC5B,IAAA,OAAO,YAAY,KAAA,EAAO,KAAA,EAAO,EAAE,IAAA,EAAM,UAAU,CAAA;AAAA,EACrD;AACA,EAAA,IAAI,eAAe,eAAA,EAAiB;AAClC,IAAA,OAAO,YAAY,KAAA,EAAO,KAAA,EAAO,EAAE,IAAA,EAAM,WAAW,CAAA;AAAA,EACtD;AAEA,EAAA,IAAI,CAAC,UAAA,CAAW,UAAA,CAAW,CAAA,EAAG,gBAAgB,GAAG,CAAA,EAAG;AAClD,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,eAAA,EAAgB;AAAA,EAC9C;AACA,EAAA,MAAM,IAAA,GAAO,UAAA,CAAW,KAAA,CAAM,gBAAA,CAAiB,SAAS,CAAC,CAAA;AACzD,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC/B,EAAA,OAAO,gBAAA,CAAiB,OAAO,QAAQ,CAAA;AACzC,CAAA;AAEA,IAAM,WAAA,GAAc,CAClB,MAAA,EACA,OAAA,EACA,KAAA,KACqB;AACrB,EAAA,IAAI,WAAW,OAAA,EAAS;AACtB,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,KAAA;AAAA,MACJ,MAAA,EAAQ,oBAAA;AAAA,MACR,cAAA,EAAgB,CAAC,OAAO;AAAA,KAC1B;AAAA,EACF;AACA,EAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,KAAA,EAAM;AAC3B,CAAA;AAEA,IAAM,gBAAA,GAAmB,CACvB,MAAA,EACA,QAAA,KACqB;AACrB,EAAA,IAAI,SAAS,MAAA,KAAW,CAAA,IAAK,QAAA,CAAS,CAAC,MAAM,EAAA,EAAI;AAC/C,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,eAAA,EAAgB;AAAA,EAC9C;AACA,EAAA,MAAM,IAAA,GAAO,SAAS,CAAC,CAAA;AACvB,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA;AAC7B,EAAA,QAAQ,IAAA;AAAM,IACZ,KAAK,MAAA;AACH,MAAA,OAAO,YAAA,CAAa,QAAQ,IAAI,CAAA;AAAA,IAClC,KAAK,QAAA;AACH,MAAA,OAAO,cAAA,CAAe,QAAQ,IAAI,CAAA;AAAA,IACpC,KAAK,KAAA;AACH,MAAA,OAAO,WAAA,CAAY,QAAQ,IAAI,CAAA;AAAA,IACjC,KAAK,WAAA;AACH,MAAA,OAAO,iBAAA,CAAkB,QAAQ,IAAI,CAAA;AAAA,IACvC,KAAK,SAAA;AACH,MAAA,OAAO,kBAAkB,MAAA,EAAQ,IAAA,EAAM,QAAQ,EAAE,IAAA,EAAM,WAAW,CAAA;AAAA,IACpE,KAAK,cAAA;AACH,MAAA,OAAO,kBAAkB,MAAA,EAAQ,IAAA,EAAM,QAAQ,EAAE,IAAA,EAAM,gBAAgB,CAAA;AAAA,IACzE;AACE,MAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,eAAA,EAAgB;AAAA;AAElD,CAAA;AAEA,IAAM,YAAA,GAAe,CACnB,MAAA,EACA,IAAA,KACqB;AACrB,EAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,IAAA,OAAO,YAAY,MAAA,EAAQ,MAAA,EAAQ,EAAE,IAAA,EAAM,cAAc,CAAA;AAAA,EAC3D;AACA,EAAA,MAAM,KAAA,GAAQ,KAAK,CAAC,CAAA;AACpB,EAAA,IAAI,UAAU,EAAA,EAAI;AAChB,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,eAAA,EAAgB;AAAA,EAC9C;AACA,EAAA,IAAI,CAAC,eAAA,CAAgB,KAAK,CAAA,EAAG;AAC3B,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,mBAAA,EAAoB;AAAA,EAClD;AACA,EAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,IAAA,OAAO,YAAY,MAAA,EAAQ,KAAA,EAAO,EAAE,IAAA,EAAM,YAAA,EAAc,OAAO,CAAA;AAAA,EACjE;AACA,EAAA,IAAI,KAAK,MAAA,KAAW,CAAA,IAAK,IAAA,CAAK,CAAC,MAAM,QAAA,EAAU;AAC7C,IAAA,OAAO,YAAY,MAAA,EAAQ,KAAA,EAAO,EAAE,IAAA,EAAM,YAAA,EAAc,OAAO,CAAA;AAAA,EACjE;AACA,EAAA,IAAI,KAAK,MAAA,KAAW,CAAA,IAAK,IAAA,CAAK,CAAC,MAAM,QAAA,EAAU;AAC7C,IAAA,OAAO,kBAAA,CAAmB,MAAA,EAAQ,KAAA,EAAO,IAAA,CAAK,CAAC,CAAE,CAAA;AAAA,EACnD;AACA,EAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,eAAA,EAAgB;AAC9C,CAAA;AAEA,IAAM,kBAAA,GAAqB,CACzB,MAAA,EACA,KAAA,EACA,OAAA,KACqB;AACrB,EAAA,QAAQ,OAAA;AAAS,IACf,KAAK,UAAA;AACH,MAAA,OAAO,YAAY,MAAA,EAAQ,MAAA,EAAQ,EAAE,IAAA,EAAM,iBAAA,EAAmB,OAAO,CAAA;AAAA,IACvE,KAAK,eAAA;AACH,MAAA,OAAO,WAAA,CAAY,QAAQ,MAAA,EAAQ;AAAA,QACjC,IAAA,EAAM,sBAAA;AAAA,QACN;AAAA,OACD,CAAA;AAAA,IACH,KAAK,YAAA;AACH,MAAA,OAAO,YAAY,MAAA,EAAQ,MAAA,EAAQ,EAAE,IAAA,EAAM,mBAAA,EAAqB,OAAO,CAAA;AAAA,IACzE,KAAK,MAAA;AACH,MAAA,OAAO,YAAY,MAAA,EAAQ,MAAA,EAAQ,EAAE,IAAA,EAAM,aAAA,EAAe,OAAO,CAAA;AAAA,IACnE;AACE,MAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,eAAA,EAAgB;AAAA;AAElD,CAAA;AAEA,IAAM,cAAA,GAAiB,CACrB,MAAA,EACA,IAAA,KACqB;AACrB,EAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,eAAA,EAAgB;AAAA,EAC9C;AACA,EAAA,MAAM,KAAA,GAAQ,KAAK,CAAC,CAAA;AACpB,EAAA,IAAI,UAAU,EAAA,EAAI;AAChB,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,eAAA,EAAgB;AAAA,EAC9C;AACA,EAAA,IAAI,CAAC,eAAA,CAAgB,KAAK,CAAA,EAAG;AAC3B,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,mBAAA,EAAoB;AAAA,EAClD;AACA,EAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,IAAA,OAAO,YAAY,MAAA,EAAQ,KAAA,EAAO,EAAE,IAAA,EAAM,iBAAA,EAAmB,OAAO,CAAA;AAAA,EACtE;AACA,EAAA,IAAI,KAAK,MAAA,KAAW,CAAA,IAAK,IAAA,CAAK,CAAC,MAAM,UAAA,EAAY;AAC/C,IAAA,OAAO,YAAY,MAAA,EAAQ,MAAA,EAAQ,EAAE,IAAA,EAAM,iBAAA,EAAmB,OAAO,CAAA;AAAA,EACvE;AACA,EAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,eAAA,EAAgB;AAC9C,CAAA;AAEA,IAAM,WAAA,GAAc,CAClB,MAAA,EACA,IAAA,KACqB;AACrB,EAAA,OAAO,kBAAkB,MAAA,EAAQ,IAAA,EAAM,QAAQ,EAAE,IAAA,EAAM,YAAW,EAAG;AAAA,IACnE;AAAA,GACD,CAAA;AACH,CAAA;AAEA,IAAM,iBAAA,GAAoB,CACxB,MAAA,EACA,IAAA,KACqB;AACrB,EAAA,OAAO,kBAAkB,MAAA,EAAQ,IAAA,EAAM,QAAQ,EAAE,IAAA,EAAM,kBAAiB,EAAG;AAAA,IACzE;AAAA,GACD,CAAA;AACH,CAAA;AAEA,IAAM,iBAAA,GAAoB,CACxB,MAAA,EACA,IAAA,EACA,eACA,KAAA,EACA,YAAA,GAAkC,EAAC,KACd;AACrB,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,YAAA,CAAa,MAAA,EAAQ;AACvC,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,eAAA,EAAgB;AAAA,EAC9C;AACA,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,YAAA,CAAa,MAAA,EAAQ,KAAK,CAAA,EAAG;AAC/C,IAAA,IAAI,IAAA,CAAK,CAAC,CAAA,KAAM,YAAA,CAAa,CAAC,CAAA,EAAG;AAC/B,MAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,eAAA,EAAgB;AAAA,IAC9C;AAAA,EACF;AACA,EAAA,OAAO,WAAA,CAAY,MAAA,EAAQ,aAAA,EAAe,KAAK,CAAA;AACjD,CAAA;;;ACxNO,IAAM,iBAAA,GAAoB,CAC/B,OAAA,KACwC;AACxC,EAAA,MAAM,QAAA,GAAgD;AAAA,IACpD,OAAA,EAAS,aAAA;AAAA,IACT,QAAQ,CAAC,EAAE,UAAS,KAAM,YAAA,CAAa,UAAU,OAAO,CAAA;AAAA,IACxD,OAAA,EAAS,aAAA;AAAA,IACT,gBAAgB,CAAC,EAAE,UAAS,KAAM,mBAAA,CAAoB,UAAU,OAAO,CAAA;AAAA,IACvE,UAAA,EAAY,qBAAqB,YAAY,CAAA;AAAA,IAC7C,UAAA,EAAY,qBAAqB,YAAY,CAAA;AAAA,IAC7C,UAAA,EAAY,aAAA;AAAA,IACZ,eAAA,EAAiB,qBAAqB,iBAAiB,CAAA;AAAA,IACvD,oBAAA,EAAsB,qBAAqB,sBAAsB,CAAA;AAAA,IACjE,iBAAA,EAAmB,qBAAqB,mBAAmB,CAAA;AAAA,IAC3D,WAAA,EAAa,qBAAqB,aAAa,CAAA;AAAA,IAC/C,eAAA,EAAiB,qBAAqB,iBAAiB,CAAA;AAAA,IACvD,eAAA,EAAiB,qBAAqB,iBAAiB,CAAA;AAAA,IACvD,QAAA,EAAU,qBAAqB,UAAU,CAAA;AAAA,IACzC,cAAA,EAAgB,qBAAqB,gBAAgB,CAAA;AAAA,IACrD,OAAA,EAAS,qBAAqB,SAAS,CAAA;AAAA,IACvC,YAAA,EAAc,qBAAqB,cAAc;AAAA,GACnD;AACA,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,aAAA,IAAiB,EAAC;AAC5C,EAAA,KAAA,MAAW,CAAC,IAAA,EAAM,OAAO,KAAK,MAAA,CAAO,OAAA,CAAQ,SAAS,CAAA,EAEnD;AACD,IAAA,QAAA,CAAS,IAAI,CAAA,GAAI,OAAA;AAAA,EACnB;AACA,EAAA,OAAO,QAAA;AACT,CAAA;AAEA,IAAM,aAAA,GAA8B,CAAC,EAAE,QAAA,EAAS,KAAM;AACpD,EAAA,iBAAA,CAAkB;AAAA,IAChB,QAAA;AAAA,IACA,UAAA,EAAY,GAAA;AAAA,IACZ,OAAA,EAAS,EAAE,MAAA,EAAQ,IAAA,EAAM,4BAAW,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAE,GAC9D,CAAA;AACH,CAAA;AAEA,IAAM,YAAA,GAAe,CACnB,QAAA,EACA,OAAA,KACS;AACT,EAAA,iBAAA,CAAkB;AAAA,IAChB,QAAA;AAAA,IACA,UAAA,EAAY,GAAA;AAAA,IACZ,OAAA,EAAS;AAAA,MACP,MAAA,EAAQ,OAAA;AAAA,MACR,WAAA,EAAa,OAAA,CAAQ,uBAAA,GAA0B,SAAA,GAAY,UAAA;AAAA,MAC3D,cAAA,EAAgB,QAAQ,WAAA,KAAgB,MAAA;AAAA,MACxC,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY;AACpC,GACD,CAAA;AACH,CAAA;AAEA,IAAM,aAAA,GAA8B,CAAC,EAAE,QAAA,EAAS,KAAM;AAIpD,EAAA,iBAAA,CAAkB;AAAA,IAChB,QAAA;AAAA,IACA,UAAA,EAAY,GAAA;AAAA,IACZ,OAAA,EAAS;AAAA,MACP,OAAA,EAAS,OAAA;AAAA,MACT,IAAA,EAAM,EAAE,KAAA,EAAO,uBAAA,EAAyB,SAAS,OAAA,EAAQ;AAAA,MACzD,OAAO;AAAC;AACV,GACD,CAAA;AACH,CAAA;AAEA,IAAM,mBAAA,GAAsB,CAC1B,QAAA,EACA,OAAA,KACS;AACT,EAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,kBAAA,GAAqB,CAAC,CAAA;AACpD,EAAA,IAAI,kBAAkB,MAAA,EAAW;AAC/B,IAAA,QAAA,CAAS,SAAA,CAAU,+BAA+B,aAAa,CAAA;AAAA,EACjE;AACA,EAAA,QAAA,CAAS,SAAA,CAAU,gCAAgC,oBAAoB,CAAA;AACvE,EAAA,QAAA,CAAS,SAAA;AAAA,IACP,8BAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,QAAA,CAAS,SAAA,CAAU,0BAA0B,KAAK,CAAA;AAClD,EAAA,QAAA,CAAS,UAAA,GAAa,GAAA;AACtB,EAAA,QAAA,CAAS,GAAA,EAAI;AACf,CAAA;AAEA,IAAM,oBAAA,GAAuB,CAAC,SAAA,KAAoC;AAChE,EAAA,OAAO,CAAC;AAAA,IACN,OAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACF,KAA2B;AACzB,IAAA,KAAK,qBAAqB,OAAA,EAAS,KAAK,CAAA,CAAE,IAAA,CAAK,CAAC,IAAA,KAAS;AACvD,MAAA,KAAA,CAAM,MAAA,CAAO;AAAA,QACX,MAAA,EAAQ,GAAG,SAAS,CAAA,SAAA,CAAA;AAAA,QACpB,OAAA,EAAS,SAAA;AAAA,QACT,OAAA,EAAS,IAAA,CAAK,EAAA,GAAK,IAAA,GAAO,QAAA;AAAA,QAC1B;AAAA,OACD,CAAA;AACD,MAAA,kBAAA,CAAmB;AAAA,QACjB,QAAA;AAAA,QACA,IAAA,EAAM,0BAAA;AAAA,QACN,OAAA,EAAS,UAAU,SAAS,CAAA,yDAAA;AAAA,OAC7B,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH,CAAA;AACF,CAAA;AAEA,IAAM,aAAA,GAA8B,CAAC,EAAE,QAAA,EAAS,KAAM;AACpD,EAAA,MAAM,GAAA,GAAM,iBAAiB,QAAQ,CAAA;AACrC,EAAA,GAAA,CAAI,WAAW,IAAM,CAAA;AACrB,EAAA,GAAA,CAAI,UAAA,CAAW;AAAA,IACb,KAAA,EAAO,MAAA;AAAA,IACP,IAAA,EAAM,EAAE,OAAA,EAAS,wCAAA,EAAyC;AAAA,IAC1D,EAAA,EAAI;AAAA,GACL,CAAA;AACD,EAAA,GAAA,CAAI,GAAA,EAAI;AACV,CAAA;AAEA,IAAM,oBAAA,GAAuB,OAC3B,OAAA,EACA,KAAA,KAC6B;AAC7B,EAAA,MAAM,QAAA,GACJ,KAAA,CAAM,IAAA,KAAS,YAAA,GACX,qBAAA,GACA,sBAAA;AACN,EAAA,MAAM,SAAS,MAAM,YAAA,CAAa,EAAE,OAAA,EAAS,UAAU,CAAA;AACvD,EAAA,OAAO,EAAE,EAAA,EAAI,MAAA,CAAO,EAAA,EAAG;AACzB,CAAA;;;AC9GO,IAAM,oCAAA,GAAuC,CAClD,OAAA,KACmC;AACnC,EAAA,MAAM,cAAc,iBAAA,CAAkB;AAAA,IACpC,iBAAA,EACE,QAAQ,iBAAA,IAAqB;AAAA,GAChC,CAAA;AACD,EAAA,MAAM,UAAA,GAAa,mBAAA,CAAoB,OAAA,CAAQ,MAAM,CAAA;AACrD,EAAA,MAAM,QAAQ,iBAAA,CAAkB;AAAA,IAC9B,KAAA,EAAO,OAAA,CAAQ,UAAA,IAAc,iBAAA,CAAkB,QAAQ,MAAM;AAAA,GAC9D,CAAA;AACD,EAAA,MAAM,QAAA,GAAW,kBAAkB,OAAO,CAAA;AAC1C,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,KAAU,MAAM,KAAK,GAAA,EAAI,CAAA;AAE/C,EAAA,OAAO,CAAC,SAAS,QAAA,KAAa;AAC5B,IAAA,KAAK,UAAA,CAAW;AAAA,MACd,OAAA;AAAA,MACA,QAAA;AAAA,MACA,OAAA;AAAA,MACA,WAAA;AAAA,MACA,UAAA;AAAA,MACA,KAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KACD,CAAA;AAAA,EACH,CAAA;AACF,CAAA;AAaA,IAAM,UAAA,GAAa,OAAO,KAAA,KAA0C;AAClE,EAAA,MAAM,EAAE,OAAA,EAAS,QAAA,EAAU,OAAA,EAAS,YAAW,GAAI,KAAA;AACnD,EAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,EAAI;AACvB,EAAA,MAAM,SAAA,GAAY,WAAW,YAAA,EAAa;AAC1C,EAAA,MAAM,SAAA,GAAY,iBAAiB,OAAO,CAAA;AAC1C,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,IAAU,KAAA;AACjC,EAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,OAAA,CAAQ,GAAA,IAAO,GAAG,CAAA;AAEnD,EAAA,IAAI;AACF,IAAA,MAAM,UAAA,GAAa,MAAM,QAAA,CAAS;AAAA,MAChC,GAAG,KAAA;AAAA,MACH,MAAA;AAAA,MACA,QAAA;AAAA,MACA,SAAA;AAAA,MACA;AAAA,KACD,CAAA;AACD,IAAA,UAAA,CAAW,GAAA,CAAI;AAAA,MACb,SAAA;AAAA,MACA,MAAA;AAAA,MACA,IAAA,EAAM,QAAA;AAAA,MACN,YAAY,QAAA,CAAS,UAAA;AAAA,MACrB,UAAA,EAAY,IAAA,CAAK,GAAA,EAAI,GAAI,KAAA;AAAA,MACzB,SAAA;AAAA,MACA,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH,SAAS,KAAA,EAAO;AACd,IAAA,qBAAA,CAAsB,EAAE,QAAA,EAAU,KAAA,EAAO,OAAA,EAAS,WAAW,CAAA;AAC7D,IAAA,UAAA,CAAW,GAAA,CAAI;AAAA,MACb,SAAA;AAAA,MACA,MAAA;AAAA,MACA,IAAA,EAAM,QAAA;AAAA,MACN,YAAY,QAAA,CAAS,UAAA;AAAA,MACrB,UAAA,EAAY,IAAA,CAAK,GAAA,EAAI,GAAI,KAAA;AAAA,MACzB;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAA;AASA,IAAM,QAAA,GAAW,OAAO,KAAA,KAA0C;AAChE,EAAA,MAAM;AAAA,IACJ,OAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAA;AAAA,IACA,WAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA;AAAA,IACA,SAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACF,GAAI,KAAA;AAEJ,EAAA,MAAM,KAAA,GAAQ,0BAAA,CAA2B,EAAE,MAAA,EAAQ,UAAU,CAAA;AAC7D,EAAA,IAAI,CAAC,MAAM,EAAA,EAAI;AACb,IAAA,gBAAA,CAAiB;AAAA,MACf,QAAA;AAAA,MACA,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,SAAS,KAAA,CAAM;AAAA,KAChB,CAAA;AACD,IAAA,OAAO,CAAA,MAAA,EAAS,MAAM,MAAM,CAAA,CAAA;AAAA,EAC9B;AAEA,EAAA,MAAM,QAAQ,KAAA,CAAM,KAAA;AACpB,EAAA,MAAM,WAAW,CAAA,EAAG,MAAA,CAAO,aAAa,CAAA,CAAA,EAAI,MAAM,IAAI,CAAA,CAAA;AACtD,EAAA,MAAM,KAAA,GAAQ,YAAY,KAAA,CAAM;AAAA,IAC9B,SAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAO,KAAA;AAAM,GACd,CAAA;AACD,EAAA,IAAI,CAAC,MAAM,EAAA,EAAI;AACb,IAAA,kBAAA,CAAmB;AAAA,MACjB,QAAA;AAAA,MACA,IAAA,EAAM,cAAA;AAAA,MACN,OAAA,EAAS,wDAAA;AAAA,MACT,cAAc,EAAE,aAAA,EAAe,MAAA,CAAO,KAAA,CAAM,iBAAiB,CAAA;AAAE,KAChE,CAAA;AACD,IAAA,OAAO,KAAA,CAAM,IAAA;AAAA,EACf;AAEA,EAAA,IAAI,iBAAA,CAAkB,KAAK,CAAA,EAAG;AAC5B,IAAA,MAAM,OAAO,gBAAA,CAAiB,EAAE,SAAS,QAAA,EAAU,OAAA,EAAS,OAAO,CAAA;AACnE,IAAA,IAAI,CAAC,KAAK,EAAA,EAAI;AACZ,MAAA,OAAO,KAAA,CAAM,IAAA;AAAA,IACf;AAAA,EACF;AAEA,EAAA,MAAM,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,CAAE;AAAA,IACzB,OAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA;AAAA,IACA,SAAA;AAAA,IACA,SAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAQ,OAAA,CAAQ;AAAA,GACjB,CAAA;AACD,EAAA,OAAO,KAAA,CAAM,IAAA;AACf,CAAA;AAEA,IAAM,iBAAA,GAAoB,CAAC,KAAA,KAA0B;AACnD,EAAA,QAAQ,MAAM,IAAA;AAAM,IAClB,KAAK,SAAA;AAAA,IACL,KAAK,QAAA;AAAA,IACL,KAAK,SAAA;AAAA,IACL,KAAK,gBAAA;AAAA,IACL,KAAK,YAAA;AAAA,IACL,KAAK,YAAA;AAAA,IACL,KAAK,iBAAA;AACH,MAAA,OAAO,KAAA;AAAA,IACT;AACE,MAAA,OAAO,IAAA;AAAA;AAEb,CAAA;AAEA,IAAM,mBAAmB,CAAC;AAAA,EACxB,OAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,KAKuB;AACrB,EAAA,IAAI,CAAC,QAAQ,uBAAA,EAAyB;AACpC,IAAA,kBAAA,CAAmB;AAAA,MACjB,QAAA;AAAA,MACA,IAAA,EAAM,uBAAA;AAAA,MACN,OAAA,EAAS;AAAA,KACV,CAAA;AACD,IAAA,OAAO,EAAE,IAAI,KAAA,EAAM;AAAA,EACrB;AACA,EAAA,MAAM,aAAa,oBAAA,CAAqB;AAAA,IACtC,OAAA;AAAA,IACA,MAAM,OAAA,CAAQ,IAAA;AAAA,IACd,MAAM,OAAA,CAAQ;AAAA,GACf,CAAA;AACD,EAAA,IAAI,CAAC,WAAW,EAAA,EAAI;AAClB,IAAA,kBAAA,CAAmB;AAAA,MACjB,QAAA;AAAA,MACA,IAAA,EAAM,WAAW,OAAA,CAAQ,KAAA;AAAA,MACzB,OAAA,EAAS,WAAW,OAAA,CAAQ,OAAA;AAAA,MAC5B,YAAY,UAAA,CAAW;AAAA,KACxB,CAAA;AACD,IAAA,OAAO,EAAE,IAAI,KAAA,EAAM;AAAA,EACrB;AACA,EAAA,MAAM,OAAO,mBAAA,CAAoB;AAAA,IAC/B,OAAA;AAAA,IACA,aAAa,OAAA,CAAQ,WAAA;AAAA,IACrB,YAAY,KAAA,CAAM;AAAA,GACnB,CAAA;AACD,EAAA,IAAI,CAAC,KAAK,EAAA,EAAI;AACZ,IAAA,kBAAA,CAAmB;AAAA,MACjB,QAAA;AAAA,MACA,IAAA,EAAM,KAAK,OAAA,CAAQ,KAAA;AAAA,MACnB,OAAA,EAAS,KAAK,OAAA,CAAQ,OAAA;AAAA,MACtB,YAAY,IAAA,CAAK,UAAA;AAAA,MACjB,GAAI,IAAA,CAAK,eAAA,KAAoB,MAAA,GACzB,EAAE,YAAA,EAAc,EAAE,kBAAA,EAAoB,IAAA,CAAK,eAAA,EAAgB,EAAE,GAC7D;AAAC,KACN,CAAA;AACD,IAAA,OAAO,EAAE,IAAI,KAAA,EAAM;AAAA,EACrB;AACA,EAAA,OAAO,EAAE,IAAI,IAAA,EAAK;AACpB,CAAA;AAEA,IAAM,mBAAmB,CAAC;AAAA,EACxB,QAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,KAIY;AACV,EAAA,IAAI,WAAW,oBAAA,EAAsB;AACnC,IAAA,kBAAA,CAAmB;AAAA,MACjB,QAAA;AAAA,MACA,IAAA,EAAM,oBAAA;AAAA,MACN,OAAA,EAAS,oDAAA;AAAA,MACT,GAAI,OAAA,KAAY,MAAA,GACZ,EAAE,YAAA,EAAc,EAAE,KAAA,EAAO,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA,EAAE,KAC5C;AAAC,KACN,CAAA;AACD,IAAA;AAAA,EACF;AACA,EAAA,MAAM,IAAA,GACJ,MAAA,KAAW,mBAAA,IAAuB,MAAA,KAAW,kBACzC,aAAA,GACA,WAAA;AACN,EAAA,MAAM,OAAA,GACJ,IAAA,KAAS,aAAA,GACL,qDAAA,GACA,qCAAA;AACN,EAAA,kBAAA,CAAmB,EAAE,QAAA,EAAU,IAAA,EAAM,OAAA,EAAS,CAAA;AAChD,CAAA;AAEA,IAAM,eAAA,GAAkB,CAAC,GAAA,KAAwB;AAC/C,EAAA,MAAM,UAAA,GAAa,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAClC,EAAA,OAAO,eAAe,EAAA,GAAK,GAAA,GAAM,GAAA,CAAI,KAAA,CAAM,GAAG,UAAU,CAAA;AAC1D,CAAA;AAEA,IAAM,iBAAA,GACJ,CAAC,MAAA,KACD,CAAC,IAAA,KAAuB;AACtB,EAAA,MAAA,CAAO,GAAA,CAAI,EAAE,KAAA,EAAO,MAAA,EAAQ,OAAA,EAAS,SAAS,IAAA,CAAK,OAAA,EAAS,CAAA,CAAA,EAAI,CAAA;AAClE,CAAA;AAEF,IAAM,wBAAwB,CAAC;AAAA,EAC7B,QAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,KAKY;AACV,EAAA,MAAM,OAAA,GAAU,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,gBAAA;AACzD,EAAA,OAAA,CAAQ,OAAO,GAAA,CAAI;AAAA,IACjB,KAAA,EAAO,OAAA;AAAA,IACP,OAAA,EAAS,4BAA4B,OAAO,CAAA,CAAA;AAAA,IAC5C;AAAA,GACD,CAAA;AACD,EAAA,IAAI,CAAC,SAAS,WAAA,EAAa;AACzB,IAAA,kBAAA,CAAmB;AAAA,MACjB,QAAA;AAAA,MACA,IAAA,EAAM,gBAAA;AAAA,MACN,OAAA,EAAS;AAAA,KACV,CAAA;AAAA,EACH,CAAA,MAAA,IAAW,CAAC,QAAA,CAAS,aAAA,EAAe;AAClC,IAAA,QAAA,CAAS,GAAA,EAAI;AAAA,EACf;AACF,CAAA;;;ACpTO,IAAM,eAAA,GAA0B,cAAA;;;ACGvC,IAAM,qBAAA,GAA2D;AAAA,EAC/D,IAAA,EAAM,QAAA;AAAA,EACN,QAAA,EAAU,CAAC,OAAA,EAAS,SAAS,CAAA;AAAA,EAC7B,UAAA,EAAY;AAAA,IACV,KAAA,EAAO,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,IACxB,OAAA,EAAS,EAAE,IAAA,EAAM,QAAA;AAAS,GAC5B;AAAA,EACA,oBAAA,EAAsB;AACxB,CAAA;AAEA,IAAM,aAAA,GAAmD;AAAA,EACvD,IAAA,EAAM,QAAA;AAAA,EACN,QAAA,EAAU,CAAC,QAAA,EAAU,aAAA,EAAe,kBAAkB,WAAW,CAAA;AAAA,EACjE,UAAA,EAAY;AAAA,IACV,QAAQ,EAAE,IAAA,EAAM,UAAU,IAAA,EAAM,CAAC,OAAO,CAAA,EAAE;AAAA,IAC1C,WAAA,EAAa,EAAE,IAAA,EAAM,QAAA,EAAU,MAAM,CAAC,SAAA,EAAW,UAAU,CAAA,EAAE;AAAA,IAC7D,cAAA,EAAgB,EAAE,IAAA,EAAM,SAAA,EAAU;AAAA,IAClC,SAAA,EAAW,EAAE,IAAA,EAAM,QAAA,EAAU,QAAQ,WAAA;AAAY,GACnD;AAAA,EACA,oBAAA,EAAsB;AACxB,CAAA;AAEA,IAAM,cAAA,GAAoD;AAAA,EACxD,IAAA,EAAM,QAAA;AAAA,EACN,QAAA,EAAU,CAAC,QAAA,EAAU,WAAW,CAAA;AAAA,EAChC,UAAA,EAAY;AAAA,IACV,QAAQ,EAAE,IAAA,EAAM,UAAU,IAAA,EAAM,CAAC,IAAI,CAAA,EAAE;AAAA,IACvC,SAAA,EAAW,EAAE,IAAA,EAAM,QAAA,EAAU,QAAQ,WAAA;AAAY,GACnD;AAAA,EACA,oBAAA,EAAsB;AACxB,CAAA;AAEA,IAAM,aAAA,GAAgB,CACpB,WAAA,MACuC;AAAA,EACvC,WAAA;AAAA,EACA,OAAA,EAAS;AAAA,IACP,kBAAA,EAAoB;AAAA,MAClB,MAAA,EAAQ,EAAE,IAAA,EAAM,4BAAA;AAA6B;AAC/C;AAEJ,CAAA,CAAA;AAEA,IAAM,UAAA,GAAa,CACjB,OAAA,EACA,WAAA,MACuC;AAAA,EACvC,IAAA,EAAM;AAAA,IACJ,OAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAU,CAAC,EAAE,UAAA,EAAY,IAAI,CAAA;AAAA,IAC7B,WAAA,EAAa;AAAA,MACX,QAAA,EAAU,IAAA;AAAA,MACV,OAAA,EAAS,EAAE,kBAAA,EAAoB,EAAE,QAAQ,EAAE,IAAA,EAAM,QAAA,EAAS,EAAE;AAAE,KAChE;AAAA,IACA,SAAA,EAAW;AAAA,MACT,KAAA,EAAO,EAAE,WAAA,EAAa,sBAAA,EAAuB;AAAA,MAC7C,KAAA,EAAO,cAAc,uCAAuC,CAAA;AAAA,MAC5D,KAAA,EAAO,cAAc,gDAAgD,CAAA;AAAA,MACrE,KAAA,EAAO,cAAc,6CAA6C,CAAA;AAAA,MAClE,KAAA,EAAO,cAAc,uCAAuC,CAAA;AAAA,MAC5D,KAAA,EAAO,cAAc,qCAAqC,CAAA;AAAA,MAC1D,KAAA,EAAO,cAAc,0CAA0C;AAAA;AACjE;AAEJ,CAAA,CAAA;AAEA,IAAM,SAAA,GAAY,CAChB,OAAA,EACA,WAAA,EACA,iBAAA,MACuC;AAAA,EACvC,GAAA,EAAK;AAAA,IACH,OAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA,EAAW;AAAA,MACT,KAAA,EAAO;AAAA,QACL,WAAA,EAAa,SAAA;AAAA,QACb,OAAA,EAAS;AAAA,UACP,oBAAoB,EAAE,MAAA,EAAQ,EAAE,IAAA,EAAM,mBAAkB;AAAE;AAC5D,OACF;AAAA,MACA,KAAA,EAAO,cAAc,qBAAqB,CAAA;AAAA,MAC1C,KAAA,EAAO,cAAc,qCAAqC;AAAA;AAC5D;AAEJ,CAAA,CAAA;AAEA,IAAM,mBAAA,GAAsB,CAC1B,OAAA,EACA,WAAA,KACsC;AACtC,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,OAAA,EAAS,WAAW,CAAA;AAG7C,EAAA,KAAA,CAAM,IAAA,CAAK,YAAY,CAAA,GAAI;AAAA,IACzB;AAAA,MACE,IAAA,EAAM,OAAA;AAAA,MACN,EAAA,EAAI,MAAA;AAAA,MACJ,QAAA,EAAU,IAAA;AAAA,MACV,MAAA,EAAQ,EAAE,IAAA,EAAM,QAAA,EAAU,SAAS,yBAAA;AAA0B;AAC/D,GACF;AACA,EAAA,OAAO,KAAA;AACT,CAAA;AAEA,IAAM,aAAa,OAA0C;AAAA,EAC3D,UAAA,EAAY,SAAA;AAAA,IACV,iBAAA;AAAA,IACA,YAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,SAAA,EAAW,SAAA;AAAA,IACT,kBAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,eAAA,EAAiB,SAAA;AAAA,IACf,+BAAA;AAAA,IACA,YAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,CAAC,CAAA,EAAG,gBAAgB,CAAA,KAAA,CAAO,GAAG,UAAA;AAAA,IAC5B,iCAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,CAAC,CAAA,EAAG,gBAAgB,CAAA,aAAA,CAAe,GAAG,SAAA;AAAA,IACpC,sBAAA;AAAA,IACA,cAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,CAAC,CAAA,EAAG,gBAAgB,CAAA,oBAAA,CAAsB,GAAG,SAAA;AAAA,IAC3C,oDAAA;AAAA,IACA,iBAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,CAAC,CAAA,EAAG,gBAAgB,CAAA,6BAAA,CAA+B,GAAG,mBAAA;AAAA,IACpD,mCAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,CAAC,CAAA,EAAG,gBAAgB,CAAA,kCAAA,CAAoC,GACtD,mBAAA;AAAA,IACE,sCAAA;AAAA,IACA;AAAA,GACF;AAAA,EACF,CAAC,CAAA,EAAG,gBAAgB,CAAA,+BAAA,CAAiC,GAAG,mBAAA;AAAA,IACtD,qCAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,CAAC,CAAA,EAAG,gBAAgB,CAAA,yBAAA,CAA2B,GAAG,mBAAA;AAAA,IAChD,6BAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,CAAC,CAAA,EAAG,gBAAgB,CAAA,eAAA,CAAiB,GAAG,SAAA;AAAA,IACtC,qCAAA;AAAA,IACA,mBAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,CAAC,CAAA,EAAG,gBAAgB,CAAA,wBAAA,CAA0B,GAAG,mBAAA;AAAA,IAC/C,2BAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,CAAC,CAAA,EAAG,gBAAgB,CAAA,SAAA,CAAW,GAAG,UAAA;AAAA,IAChC,wDAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,CAAC,CAAA,EAAG,gBAAgB,CAAA,eAAA,CAAiB,GAAG,UAAA;AAAA,IACtC,6CAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,CAAC,CAAA,EAAG,gBAAgB,CAAA,QAAA,CAAU,GAAG,UAAA;AAAA,IAC/B,wBAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA,CAAC,CAAA,EAAG,gBAAgB,CAAA,aAAA,CAAe,GAAG,UAAA;AAAA,IACpC,4CAAA;AAAA,IACA;AAAA;AAEJ,CAAA,CAAA;AAEO,IAAM,uBAAuB,OAAwB;AAAA,EAC1D,OAAA,EAAS,OAAA;AAAA,EACT,IAAA,EAAM;AAAA,IACJ,KAAA,EAAO,uBAAA;AAAA,IACP,OAAA,EAAS,eAAA;AAAA,IACT,WAAA,EACE;AAAA,GAGJ;AAAA,EACA,OAAA,EAAS,CAAC,EAAE,GAAA,EAAK,yBAAyB,CAAA;AAAA,EAC1C,UAAA,EAAY;AAAA,IACV,eAAA,EAAiB;AAAA,MACf,UAAA,EAAY;AAAA,QACV,IAAA,EAAM,MAAA;AAAA,QACN,MAAA,EAAQ;AAAA;AACV,KACF;AAAA,IACA,OAAA,EAAS;AAAA,MACP,KAAA,EAAO,qBAAA;AAAA,MACP,MAAA,EAAQ,aAAA;AAAA,MACR,OAAA,EAAS;AAAA;AACX,GACF;AAAA,EACA,OAAO,UAAA;AACT,CAAA,CAAA;;;AC/JO,IAAM,4BAAA,GAA+B,OAC1C,OAAA,KACoC;AACpC,EAAA,MAAM,IAAA,GAAO,QAAQ,IAAA,IAAQ,YAAA;AAC7B,EAAA,MAAM,aAAA,GAAgB,QAAQ,IAAA,IAAQ,YAAA;AACtC,EAAA,MAAM,uBAAA,GACJ,OAAA,CAAQ,uBAAA,IACR,8BAAA,CAA+B,QAAQ,GAAG,CAAA;AAE5C,EAAA,MAAM,cAAA,GAA+B,CAAC,EAAE,QAAA,EAAS,KAAM;AACrD,IAAA,iBAAA,CAAkB;AAAA,MAChB,QAAA;AAAA,MACA,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,oBAAA;AAAqB,KAC/B,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,mBAAA,GACJ;AAAA,IACE,OAAA,EAAS,cAAA;AAAA,IACT,GAAI,OAAA,CAAQ,aAAA,IAAiB;AAAC,GAChC;AAEF,EAAA,MAAM,UAAU,oCAAA,CAAqC;AAAA,IACnD,IAAA;AAAA,IACA,IAAA,EAAM,aAAA;AAAA,IACN,QAAQ,OAAA,CAAQ,MAAA;AAAA,IAChB,uBAAA;AAAA,IACA,iBAAA,EACE,QAAQ,iBAAA,IAAqB,6BAAA;AAAA,IAC/B,aAAA,EAAe,mBAAA;AAAA,IACf,GAAI,QAAQ,WAAA,KAAgB,MAAA,GACxB,EAAE,WAAA,EAAa,OAAA,CAAQ,WAAA,EAAY,GACnC,EAAC;AAAA,IACL,GAAI,QAAQ,UAAA,KAAe,MAAA,GACvB,EAAE,UAAA,EAAY,OAAA,CAAQ,UAAA,EAAW,GACjC,EAAC;AAAA,IACL,GAAI,QAAQ,kBAAA,KAAuB,MAAA,GAC/B,EAAE,kBAAA,EAAoB,OAAA,CAAQ,kBAAA,EAAmB,GACjD;AAAC,GACN,CAAA;AAED,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,CAAC,OAAA,EAAS,QAAA,KAAa;AACjD,IAAA,OAAA,CAAQ,SAAS,QAAQ,CAAA;AAAA,EAC3B,CAAC,CAAA;AAED,EAAA,MAAM,MAAA,CAAO,MAAA,EAAQ,IAAA,EAAM,aAAa,CAAA;AACxC,EAAA,MAAM,OAAA,GAAU,OAAO,OAAA,EAAQ;AAC/B,EAAA,MAAM,YACJ,OAAA,KAAY,IAAA,IAAQ,OAAO,OAAA,KAAY,QAAA,GACnC,QAAQ,IAAA,GACR,aAAA;AACN,EAAA,MAAM,GAAA,GAAM,UAAU,UAAA,CAAW,IAAI,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,SAAS,CAAC,CAAA,CAAA;AAC3D,EAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAE3B,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA,EAAM,SAAA;AAAA,IACN,GAAA;AAAA,IACA,SAAA;AAAA,IACA,KAAA,EAAO,MAAM,WAAA,CAAY,MAAM;AAAA,GACjC;AACF,CAAA;AAEA,IAAM,MAAA,GAAS,CAAC,MAAA,EAAgB,IAAA,EAAc,IAAA,KAAgC;AAC5E,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,OAAA,GAAU,CAAC,KAAA,KAAuC;AACtD,MAAA,MAAA,CAAO,GAAA,CAAI,aAAa,WAAW,CAAA;AACnC,MAAA,MAAA,CAAO,KAAK,CAAA;AAAA,IACd,CAAA;AACA,IAAA,MAAM,cAAc,MAAY;AAC9B,MAAA,MAAA,CAAO,GAAA,CAAI,SAAS,OAAO,CAAA;AAC3B,MAAA,OAAA,EAAQ;AAAA,IACV,CAAA;AACA,IAAA,MAAA,CAAO,IAAA,CAAK,SAAS,OAAO,CAAA;AAC5B,IAAA,MAAA,CAAO,IAAA,CAAK,aAAa,WAAW,CAAA;AACpC,IAAA,MAAA,CAAO,MAAA,CAAO,MAAM,IAAI,CAAA;AAAA,EAC1B,CAAC,CAAA;AACH,CAAA;AAEA,IAAM,WAAA,GAAc,CAAC,MAAA,KAAkC;AACrD,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAA,CAAO,KAAA,CAAM,CAAC,KAAA,KAAU;AACtB,MAAA,IAAI,UAAU,MAAA,EAAW;AACvB,QAAA,MAAA,CAAO,KAAK,CAAA;AACZ,QAAA;AAAA,MACF;AACA,MAAA,OAAA,EAAQ;AAAA,IACV,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH,CAAA;AAEA,IAAM,UAAA,GAAa,CAAC,IAAA,KAAyB;AAC3C,EAAA,IAAI,IAAA,CAAK,SAAS,GAAG,CAAA,IAAK,CAAC,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG;AAC/C,IAAA,OAAO,IAAI,IAAI,CAAA,CAAA,CAAA;AAAA,EACjB;AACA,EAAA,OAAO,IAAA;AACT,CAAA;;;ACtIO,IAAM,iBAAA,GAA4B;AAGzC,IAAM,YAAA,GAAuB,KAAA;AAG7B,IAAM,kBAAA,GAAqB;AAAA,EACzB,IAAA,EAAM,SAAA;AAAA,EACN,IAAA,EAAM,IAAA;AAAA,EACN,iBAAA,EAAmB,EAAA;AAAA,EACnB,SAAA,EAAW,MAAA;AAAA,EACX,QAAA,EAAU;AACZ,CAAA;AAcO,IAAM,2BAAA,GAAN,cAA0C,KAAA,CAAM;AAAA,EACrC,OAAA;AAAA,EAET,WAAA,CAAY,SAAiB,MAAA,EAAgB;AAClD,IAAA,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,EAAA,EAAK,MAAM,CAAA,CAAE,CAAA;AAC7B,IAAA,IAAA,CAAK,IAAA,GAAO,6BAAA;AACZ,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AAAA,EACjB;AACF;AAEA,IAAM,kBAAkB,CAAC;AAAA,EACvB,GAAA;AAAA,EACA,OAAA;AAAA,EACA,GAAA;AAAA,EACA;AACF,CAAA,KAKc;AACZ,EAAA,IAAI,CAAC,SAAA,CAAU,IAAA,CAAK,GAAG,CAAA,EAAG;AACxB,IAAA,MAAM,IAAI,2BAAA;AAAA,MACR,OAAA;AAAA,MACA,kCAAkC,GAAG,CAAA,CAAA;AAAA,KACvC;AAAA,EACF;AACA,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,QAAA,CAAS,GAAA,EAAK,EAAE,CAAA;AACrC,EAAA,IAAI,KAAA,GAAQ,GAAA,IAAO,KAAA,GAAQ,GAAA,EAAK;AAC9B,IAAA,MAAM,IAAI,2BAAA;AAAA,MACR,OAAA;AAAA,MACA,CAAA,YAAA,EAAe,MAAA,CAAO,GAAG,CAAC,CAAA,EAAA,EAAK,MAAA,CAAO,GAAG,CAAC,CAAA,YAAA,EAAe,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,KACxE;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT,CAAA;AAEA,IAAM,cAAA,GAAiB,CAAC,GAAA,KAAiC;AACvD,EAAA,IAAI,GAAA,KAAQ,MAAA,IAAU,GAAA,KAAQ,MAAA,EAAQ;AACpC,IAAA,OAAO,GAAA;AAAA,EACT;AACA,EAAA,MAAM,IAAI,2BAAA;AAAA,IACR,8BAAA;AAAA,IACA,wCAAwC,GAAG,CAAA,CAAA;AAAA,GAC7C;AACF,CAAA;AAEA,IAAM,mBAAmB,CAAC,GAAA,KACxB,IACG,KAAA,CAAM,GAAG,EACT,GAAA,CAAI,CAAC,YAAY,OAAA,CAAQ,IAAA,EAAM,CAAA,CAC/B,MAAA,CAAO,CAAC,OAAA,KAAY,OAAA,CAAQ,SAAS,CAAC,CAAA;AAOpC,IAAM,2BAAA,GAA8B,CACzC,GAAA,KAC2B;AAC3B,EAAA,MAAM,OAAA,GAAU,IAAI,wBAAwB,CAAA;AAC5C,EAAA,MAAM,OAAA,GAAU,IAAI,wBAAwB,CAAA;AAC5C,EAAA,MAAM,MAAA,GAAS,IAAI,uCAAuC,CAAA;AAC1D,EAAA,MAAM,OAAA,GAAU,IAAI,gCAAgC,CAAA;AACpD,EAAA,MAAM,SAAA,GAAY,IAAI,gCAAgC,CAAA;AACtD,EAAA,MAAM,SAAA,GAAY,IAAI,8BAA8B,CAAA;AAEpD,EAAA,MAAM,OACJ,OAAA,KAAY,MAAA,IAAa,QAAQ,MAAA,GAAS,CAAA,GACtC,UACA,kBAAA,CAAmB,IAAA;AAEzB,EAAA,MAAM,OACJ,OAAA,KAAY,MAAA,IAAa,OAAA,CAAQ,MAAA,GAAS,IACtC,eAAA,CAAgB;AAAA,IACd,GAAA,EAAK,OAAA;AAAA,IACL,OAAA,EAAS,wBAAA;AAAA,IACT,GAAA,EAAK,CAAA;AAAA,IACL,GAAA,EAAK;AAAA,GACN,IACD,kBAAA,CAAmB,IAAA;AAEzB,EAAA,MAAM,oBACJ,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,MAAA,GAAS,IACpC,eAAA,CAAgB;AAAA,IACd,GAAA,EAAK,MAAA;AAAA,IACL,OAAA,EAAS,uCAAA;AAAA,IACT,GAAA,EAAK,CAAA;AAAA,IACL,GAAA,EAAK;AAAA,GACN,IACD,kBAAA,CAAmB,iBAAA;AAEzB,EAAA,MAAM,qBACJ,OAAA,KAAY,MAAA,GAAY,gBAAA,CAAiB,OAAO,IAAI,EAAC;AAEvD,EAAA,MAAM,cACJ,SAAA,KAAc,MAAA,IAAa,SAAA,CAAU,MAAA,GAAS,IAAI,SAAA,GAAY,MAAA;AAEhE,EAAA,MAAM,SAAA,GACJ,cAAc,MAAA,IAAa,SAAA,CAAU,SAAS,CAAA,GAC1C,cAAA,CAAe,SAAS,CAAA,GACxB,kBAAA,CAAmB,SAAA;AAEzB,EAAA,OAAO;AAAA,IACL,IAAA;AAAA,IACA,IAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA;AAAA,IACA,UAAU,kBAAA,CAAmB;AAAA,GAC/B;AACF;AAGO,IAAM,mBAAA,GAA8B,CAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAuBpC,IAAM,sBAAsB,MAAc;AAG1C,IAAM,mBAAA,GAAsB,CAAC,IAAA,KAAyC;AAC3E,EAAA,MAAM,KAAA,GAAQ,KAAK,CAAC,CAAA;AACpB,EAAA,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA;AAC3D;AAcA,IAAM,UAAA,GAAiC;AAAA,EACrC,MAAA,EAAQ,CAAC,IAAA,KAAS;AAChB,IAAA,OAAA,CAAQ,MAAA,CAAO,MAAM,IAAI,CAAA;AAAA,EAC3B,CAAA;AAAA,EACA,MAAA,EAAQ,CAAC,IAAA,KAAS;AAChB,IAAA,OAAA,CAAQ,MAAA,CAAO,MAAM,IAAI,CAAA;AAAA,EAC3B;AACF,CAAA;AASO,IAAM,sBAAsB,OAAO;AAAA,EACxC,IAAA;AAAA,EACA,GAAA;AAAA,EACA,EAAA,GAAK,UAAA;AAAA,EACL;AACF,CAAA,KAUuC;AACrC,EAAA,IAAI,mBAAA,CAAoB,IAAI,CAAA,EAAG;AAC7B,IAAA,EAAA,CAAG,MAAA,CAAO,qBAAqB,CAAA;AAC/B,IAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,MAAA,EAAO;AAAA,EACvC;AAEA,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,4BAA4B,GAAG,CAAA;AAAA,EAC1C,SAAS,KAAA,EAAO;AACd,IAAA,IAAI,iBAAiB,2BAAA,EAA6B;AAChD,MAAA,EAAA,CAAG,MAAA,CAAO,CAAA,OAAA,EAAU,KAAA,CAAM,OAAO;AAAA,CAAI,CAAA;AACrC,MAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,cAAA,EAAe;AAAA,IAC/C;AACA,IAAA,MAAM,KAAA;AAAA,EACR;AAEA,EAAA,MAAM,SAAS,qBAAA,CAAsB;AAAA,IACnC,QAAQ,MAAA,CAAO,SAAA;AAAA,IACf,OAAO,MAAA,CAAO;AAAA,GACf,CAAA;AAED,EAAA,IAAI,YAAA;AACJ,EAAA,IAAI;AACF,IAAA,YAAA,GAAe,MAAM,4BAAA,CAA6B;AAAA,MAChD,MAAM,MAAA,CAAO,IAAA;AAAA,MACb,MAAM,MAAA,CAAO,IAAA;AAAA,MACb,MAAA;AAAA,MACA,mBAAmB,MAAA,CAAO,iBAAA;AAAA,MAC1B,oBAAoB,MAAA,CAAO,kBAAA;AAAA,MAC3B,GAAI,OAAO,WAAA,KAAgB,KAAA,CAAA,GACvB,EAAE,WAAA,EAAa,MAAA,CAAO,WAAA,EAAY,GAClC,EAAC;AAAA,MACL;AAAA,KACD,CAAA;AAAA,EACH,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,UAAU,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACrE,IAAA,MAAA,CAAO,GAAA,CAAI;AAAA,MACT,KAAA,EAAO,OAAA;AAAA,MACP,OAAA,EAAS,2BAA2B,OAAO,CAAA,CAAA;AAAA,MAC3C,KAAA,EAAO;AAAA,KACR,CAAA;AACD,IAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,YAAA,EAAa;AAAA,EAC7C;AAEA,EAAA,MAAA,CAAO,GAAA,CAAI;AAAA,IACT,KAAA,EAAO,MAAA;AAAA,IACP,OAAA,EAAS,CAAA,aAAA,EAAgB,YAAA,CAAa,GAAG,CAAA,CAAA;AAAA,IACzC,KAAA,EAAO;AAAA,GACR,CAAA;AAED,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,MAAM,OAAA,CAAQ;AAAA,MACZ,OAAO,YAAA,CAAa,KAAA;AAAA,MACpB,MAAM,YAAA,CAAa,IAAA;AAAA,MACnB,MAAM,YAAA,CAAa;AAAA,KACpB,CAAA;AACD,IAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,QAAA,EAAS;AAAA,EACzC;AAEA,EAAA,MAAM,eAAA,CAAgB,cAAc,MAAM,CAAA;AAC1C,EAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,QAAA,EAAS;AACzC;AAEA,IAAM,kBAAkB,CACtB,YAAA,EAGA,WAEA,IAAI,OAAA,CAAc,CAAC,OAAA,KAAY;AAC7B,EAAA,IAAI,YAAA,GAAe,KAAA;AACnB,EAAA,MAAM,QAAA,GAAW,CAAC,MAAA,KAAiC;AACjD,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA;AAAA,IACF;AACA,IAAA,YAAA,GAAe,IAAA;AACf,IAAA,MAAA,CAAO,GAAA,CAAI;AAAA,MACT,KAAA,EAAO,MAAA;AAAA,MACP,OAAA,EAAS,YAAY,MAAM,CAAA,UAAA,CAAA;AAAA,MAC3B,KAAA,EAAO;AAAA,KACR,CAAA;AACD,IAAA,MAAM,QAAA,GAAW,WAAW,MAAM;AAChC,MAAA,MAAA,CAAO,GAAA,CAAI;AAAA,QACT,KAAA,EAAO,MAAA;AAAA,QACP,OAAA,EAAS,CAAA,aAAA,EAAgB,MAAA,CAAO,iBAAiB,CAAC,CAAA,WAAA,CAAA;AAAA,QAClD,KAAA,EAAO;AAAA,OACR,CAAA;AACD,MAAA,OAAA,EAAQ;AAAA,IACV,GAAG,iBAAiB,CAAA;AACpB,IAAA,QAAA,CAAS,KAAA,EAAM;AACf,IAAA,YAAA,CACG,KAAA,EAAM,CACN,IAAA,CAAK,MAAM;AACV,MAAA,YAAA,CAAa,QAAQ,CAAA;AACrB,MAAA,MAAA,CAAO,GAAA,CAAI;AAAA,QACT,KAAA,EAAO,MAAA;AAAA,QACP,OAAA,EAAS,SAAA;AAAA,QACT,KAAA,EAAO;AAAA,OACR,CAAA;AACD,MAAA,OAAA,EAAQ;AAAA,IACV,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,KAAmB;AACzB,MAAA,YAAA,CAAa,QAAQ,CAAA;AACrB,MAAA,MAAM,UACJ,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACvD,MAAA,MAAA,CAAO,GAAA,CAAI;AAAA,QACT,KAAA,EAAO,OAAA;AAAA,QACP,OAAA,EAAS,iBAAiB,OAAO,CAAA,CAAA;AAAA,QACjC,KAAA,EAAO;AAAA,OACR,CAAA;AACD,MAAA,OAAA,EAAQ;AAAA,IACV,CAAC,CAAA;AAAA,EACL,CAAA;AACA,EAAA,OAAA,CAAQ,IAAA,CAAK,WAAW,QAAQ,CAAA;AAChC,EAAA,OAAA,CAAQ,IAAA,CAAK,UAAU,QAAQ,CAAA;AACjC,CAAC,CAAA;AAaI,IAAM,2BAA2B,CAAC;AAAA,EACvC,KAAA;AAAA,EACA;AACF,CAAA,KAGe;AACb,EAAA,IAAI,UAAU,MAAA,EAAW;AACvB,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,aAAa,KAAK,CAAA;AAAA,EAC/B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,SAAA,KAAc,aAAA,CAAc,QAAQ,CAAA,CAAE,IAAA;AAC/C;AAEA,IACE,wBAAA,CAAyB;AAAA,EACvB,KAAA,EAAO,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA;AAAA,EACrB,WAAW,MAAA,CAAA,IAAA,CAAY;AACzB,CAAC,CAAA,EACD;AACA,EAAA,KAAK,mBAAA,CAAoB,EAAE,IAAA,EAAM,OAAA,CAAQ,KAAK,KAAA,CAAM,CAAC,CAAA,EAAG,GAAA,EAAK,QAAQ,GAAA,EAAK,CAAA,CACvE,IAAA,CAAK,CAAC,MAAA,KAAW;AAChB,IAAA,OAAA,CAAQ,IAAA,CAAK,OAAO,QAAQ,CAAA;AAAA,EAC9B,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,KAAmB;AACzB,IAAA,MAAM,UAAU,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACrE,IAAA,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,OAAA,EAAU,OAAO;AAAA,CAAI,CAAA;AAC1C,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB,CAAC,CAAA;AACL","file":"server-entrypoint.js","sourcesContent":["/**\n * Label-anchored secret patterns. Each captures `(label)(value)` so the\n * label is preserved in the output while the value is replaced with the\n * caller-supplied placeholder.\n */\nconst SHARED_SECRET_PATTERNS = [\n /(\\b(?:access_token|oauth_token|refresh_token|client_secret)\\s*=\\s*)([^\\s]+)/gi,\n /(\\b(?:repoToken|figmaAccessToken|token)\\s*=\\s*)([^\\s]+)/gi,\n /(\\bauthorization\\s*:\\s*(?:bearer|basic|oauth)\\s+)([^\\s]+)/gi,\n /(\\bx-access-token\\s*:\\s*)([^\\s]+)/gi,\n /(\\b(?:Bearer|Token|Secret|Api[-_ ]?Key|Password)\\b\\s*[:=]\\s*)([A-Za-z0-9._-]{8,})\\b/gi,\n /(\\b(?:Bearer|Token|OAuth)\\s+)([A-Za-z0-9._-]{8,})\\b/gi,\n /(\"(?:repoToken|figmaAccessToken|token|secret|api[-_ ]?key|password|authorization|x-figma-token)\"\\s*:\\s*\")((?:[^\"\\\\]|\\\\.)+)/gi,\n] as const;\n\n/**\n * Issue #1667 (audit-2026-05): bare-token shape patterns. These match\n * on the credential's intrinsic shape, not on a label. Applied AFTER the\n * label-anchored patterns so the existing pretty-print form (\"Bearer\n * <jwt>\") still preserves the surrounding label, but a bare credential\n * smuggled into a free-text error message (Azure AI Foundry 401 bodies,\n * fetch stack traces, sanitized JSON whose label was stripped upstream)\n * still gets redacted.\n *\n * Coverage:\n * - JWT (3 base64url segments, header floor 16 chars, body/sig floor 16\n * chars). Header byte 0..2 is `eyJ` because every base64url-encoded\n * `{\"` JSON object header begins that way.\n * - GitHub PAT, OAuth user-to-server, server-to-server, refresh, app\n * installation tokens (`gh[psoru]_...`).\n * - Figma personal access token (`figd_...`).\n * - Slack bot/user tokens (`xox[bp]-...`).\n * - Atlassian / Jira PAT (`ATATT3...`).\n * - AWS access key id (`AKIA...` / `ASIA...` 16-32 alnum total).\n * - Azure storage SAS-style token shape (`sig=...`) — covered by the\n * label-anchored set, so not duplicated here.\n *\n * Each pattern uses `\\b` boundaries so a substring inside a longer alnum\n * sequence is not falsely matched.\n */\nconst BARE_TOKEN_PATTERNS = [\n // JWT — three base64url segments separated by dots. Floor of 16 chars\n // per segment to avoid matching short ID-like strings; real JWTs are\n // far longer.\n /\\beyJ[A-Za-z0-9_-]{16,}\\.[A-Za-z0-9_-]{16,}\\.[A-Za-z0-9_-]{16,}\\b/g,\n // GitHub Personal Access Token / installation / OAuth / refresh tokens.\n /\\bgh[psoru]_[A-Za-z0-9]{36,}\\b/g,\n // Figma personal access token. Cannot use trailing `\\b` because the\n // token alphabet includes `-` and `_`, both non-word for the `_`\n // (actually `_` is a word char but `-` is not) — when the token ends\n // on `-` the `\\b` would not match. Negative lookahead instead.\n /\\bfigd_[A-Za-z0-9_-]{40,}(?![A-Za-z0-9_-])/g,\n // Slack bot/user/app-level tokens. Trailing `-` would break `\\b`; use\n // a negative lookahead.\n /\\bxox[bpoasr]-[A-Za-z0-9-]{20,}(?![A-Za-z0-9-])/g,\n // Atlassian / Jira PAT. Cannot use `\\b` on the right because the\n // token alphabet includes `=` (base64 padding), which is itself a\n // non-word character — `\\b` between two non-word chars never fires.\n // Use a negative lookahead on the token-alphabet instead.\n /\\bATATT3[A-Za-z0-9_=-]{40,}(?![A-Za-z0-9_=-])/g,\n // AWS access-key id (`AKIA...` for IAM users, `ASIA...` for STS).\n /\\b(?:AKIA|ASIA)[A-Z0-9]{16}\\b/g,\n];\n\nexport const redactHighRiskSecrets = (\n message: string,\n replacement: string,\n): string => {\n // 1. Label-anchored patterns run first — they preserve the leading\n // label fragment via `$1${replacement}` so the diagnostic remains\n // readable.\n const labeled = SHARED_SECRET_PATTERNS.reduce((redacted, pattern) => {\n return redacted.replace(pattern, `$1${replacement}`);\n }, message);\n // 2. Bare-token patterns run second so any unlabelled credential\n // that survived step 1 (e.g. a JWT smuggled into an Azure error\n // body, a bare `figd_...` quoted in a stack trace) is replaced\n // in full by the placeholder.\n return BARE_TOKEN_PATTERNS.reduce((redacted, pattern) => {\n return redacted.replace(pattern, replacement);\n }, labeled);\n};\n","import type {\n WorkspaceJobStageName,\n WorkspaceLogFormat,\n} from \"@oscharko-dev/ti-contracts\";\nimport { redactHighRiskSecrets } from \"./secret-redaction.js\";\n\nexport type WorkspaceRuntimeLogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nexport interface WorkspaceRuntimeLogInput {\n level: WorkspaceRuntimeLogLevel;\n message: string;\n jobId?: string;\n stage?: WorkspaceJobStageName;\n requestId?: string;\n event?: string;\n method?: string;\n path?: string;\n statusCode?: number;\n}\n\nexport interface WorkspaceRuntimeLogger {\n log: (input: WorkspaceRuntimeLogInput) => void;\n}\n\nexport const DEFAULT_WORKSPACE_LOG_FORMAT: WorkspaceLogFormat = \"text\";\nconst DEFAULT_WORKSPACE_LOG_LEVEL: WorkspaceRuntimeLogLevel = \"info\";\nexport const TEST_INTELLIGENCE_LOG_LEVEL_ENV =\n \"TEST_INTELLIGENCE_LOG_LEVEL\" as const;\nconst WORKSPACE_RUNTIME_LOG_LEVEL_PRIORITY: Record<\n WorkspaceRuntimeLogLevel,\n number\n> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n};\n\nconst formatTextLine = ({\n level,\n label,\n message,\n jobId,\n stage,\n requestId,\n event,\n method,\n path,\n statusCode,\n}: {\n level: WorkspaceRuntimeLogLevel;\n label: string;\n message: string;\n jobId?: string;\n stage?: WorkspaceJobStageName;\n requestId?: string;\n event?: string;\n method?: string;\n path?: string;\n statusCode?: number;\n}): string => {\n const segments = [`[${label}]`];\n if (level !== \"info\") {\n segments.push(`[${level}]`);\n }\n if (jobId) {\n segments.push(`[job=${jobId}]`);\n }\n if (stage) {\n segments.push(`[stage=${stage}]`);\n }\n if (requestId) {\n segments.push(`[request=${requestId}]`);\n }\n if (event) {\n segments.push(`[event=${event}]`);\n }\n if (method) {\n segments.push(`[method=${method}]`);\n }\n if (path) {\n segments.push(`[path=${path}]`);\n }\n if (statusCode !== undefined) {\n segments.push(`[status=${statusCode}]`);\n }\n return `${segments.join(\"\")} ${message}\\n`;\n};\n\nexport const redactLogMessage = (message: string): string => {\n return redactHighRiskSecrets(message, \"[REDACTED]\");\n};\n\nexport const resolveWorkspaceLogFormat = ({\n value,\n fallback = DEFAULT_WORKSPACE_LOG_FORMAT,\n}: {\n value: string | undefined;\n fallback?: WorkspaceLogFormat;\n}): WorkspaceLogFormat => {\n const normalized = value?.trim().toLowerCase();\n if (normalized === \"json\") {\n return \"json\";\n }\n if (normalized === \"text\") {\n return \"text\";\n }\n return fallback;\n};\n\nexport const resolveWorkspaceLogLevel = ({\n value,\n fallback = DEFAULT_WORKSPACE_LOG_LEVEL,\n}: {\n value: string | undefined;\n fallback?: WorkspaceRuntimeLogLevel;\n}): WorkspaceRuntimeLogLevel => {\n const normalized = value?.trim().toLowerCase();\n if (\n normalized === \"debug\" ||\n normalized === \"info\" ||\n normalized === \"warn\" ||\n normalized === \"error\"\n ) {\n return normalized;\n }\n return fallback;\n};\n\nexport const createWorkspaceLogger = ({\n format = DEFAULT_WORKSPACE_LOG_FORMAT,\n minLevel,\n now = () => new Date().toISOString(),\n stdoutWriter = (line: string) => {\n process.stdout.write(line);\n },\n stderrWriter = (line: string) => {\n process.stderr.write(line);\n },\n label = \"test-intelligence\",\n}: {\n format?: WorkspaceLogFormat;\n now?: () => string;\n stdoutWriter?: (line: string) => void;\n stderrWriter?: (line: string) => void;\n label?: string;\n minLevel?: WorkspaceRuntimeLogLevel;\n} = {}): WorkspaceRuntimeLogger => {\n const resolvedMinLevel = resolveWorkspaceLogLevel({\n value: minLevel ?? process.env[TEST_INTELLIGENCE_LOG_LEVEL_ENV],\n fallback: DEFAULT_WORKSPACE_LOG_LEVEL,\n });\n\n return {\n log: ({\n level,\n message,\n jobId,\n stage,\n requestId,\n event,\n method,\n path,\n statusCode,\n }) => {\n if (\n WORKSPACE_RUNTIME_LOG_LEVEL_PRIORITY[level] <\n WORKSPACE_RUNTIME_LOG_LEVEL_PRIORITY[resolvedMinLevel]\n ) {\n return;\n }\n\n const sanitizedMessage = redactLogMessage(message);\n const line =\n format === \"json\"\n ? `${JSON.stringify({\n ts: now(),\n level,\n msg: sanitizedMessage,\n ...(jobId ? { jobId } : {}),\n ...(stage ? { stage } : {}),\n ...(requestId ? { requestId } : {}),\n ...(event ? { event } : {}),\n ...(method ? { method } : {}),\n ...(path ? { path } : {}),\n ...(statusCode !== undefined ? { statusCode } : {}),\n })}\\n`\n : formatTextLine({\n level,\n label,\n message: sanitizedMessage,\n ...(jobId ? { jobId } : {}),\n ...(stage ? { stage } : {}),\n ...(requestId ? { requestId } : {}),\n ...(event ? { event } : {}),\n ...(method ? { method } : {}),\n ...(path ? { path } : {}),\n ...(statusCode !== undefined ? { statusCode } : {}),\n });\n\n if (level === \"warn\" || level === \"error\") {\n stderrWriter(line);\n return;\n }\n stdoutWriter(line);\n },\n };\n};\n","/**\n * Semantic-content sanitization for LLM-generated test-case strings (Issue #1413).\n *\n * The structural schema validator (`generated-test-case-schema.ts`) confirms\n * that `steps[n].action`, `steps[n].expected`, top-level `expectedResults[n]`,\n * `preconditions[n]`, and `testData[n]` are non-empty strings, but does not\n * inspect their *content*. A schema-valid response can still carry\n * shell-injection-shape strings (`rm -rf /`, `;rm -rf;`, `$(curl ...)`),\n * curly-bracket payloads (`${jndi:ldap://...}`), long base64 / hex blobs\n * (likely encoded payloads), `<script>` tags, inline event handlers, or\n * `javascript:` / `data:` URLs. None of those belong in a generated QC test\n * case. Reviewers catch them today only through attention.\n *\n * This module is a defensive, hand-rolled detector that flags those patterns\n * at the validation layer so the pipeline blocks early. The detector is\n * intentionally bounded:\n *\n * - Pure: no IO, no allocation in hot loops, ASCII-folded inputs.\n * - Fail-loud: any match becomes an `error`-severity validation issue\n * and the validation report blocks downstream gates by default.\n * - Override-aware: a reviewer with the necessary authority can record a\n * structured `note` review event (kind = `\"note\"`, metadata\n * `overrideKind = \"semantic_suspicious_content\"` plus a non-empty\n * `justification`) that the policy gate respects on subsequent\n * evaluation. The validation artifact still carries the original\n * finding so the audit history is preserved.\n *\n * The patterns are deliberately conservative — every category cites the\n * concrete attack class it represents and is documented in the constants\n * below so reviewers can audit changes by reading the source.\n */\n\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\n\nimport type {\n ReviewEvent,\n ReviewEventKind,\n ReviewGateSnapshot,\n TestCaseValidationReport,\n} from \"@oscharko-dev/ti-contracts\";\nimport { canonicalJson } from \"./content-hash.js\";\n\n// Structural interface for the review-store adapter. Defined here to keep the\n// security package independent of the Review module (#16). The shape must\n// remain structurally compatible with ReviewStore in src/test-intelligence/review-store.ts;\n// deviation will surface as a compile-time error at call sites in the Review module.\nexport interface RecordTransitionInput {\n jobId: string;\n testCaseId?: string;\n kind: ReviewEventKind;\n at: string;\n actor?: string;\n note?: string;\n metadata?: Record<string, string | number | boolean | null>;\n}\n\nexport type RecordTransitionResult =\n | { ok: true; event: ReviewEvent; snapshot: ReviewGateSnapshot }\n | { ok: false; code: string };\n\nexport interface ReviewStore {\n recordTransition(\n input: RecordTransitionInput,\n ): Promise<RecordTransitionResult>;\n}\n\n/**\n * Stable category labels for matched suspicious content. Each label is also\n * persisted as the `overrideCategory` metadata key on a reviewer override\n * note so the audit log records which deny-list category was waived.\n */\nexport const SEMANTIC_SUSPICION_CATEGORIES = [\n \"shell_metacharacters\",\n \"command_substitution\",\n \"jndi_log4shell\",\n \"encoded_payload_base64\",\n \"encoded_payload_hex\",\n \"script_tag\",\n \"html_event_handler\",\n \"dangerous_url_scheme\",\n] as const;\n\nexport type SemanticSuspicionCategory =\n (typeof SEMANTIC_SUSPICION_CATEGORIES)[number];\n\n/**\n * Single deny-list match.\n *\n * `matchedSnippet` is a short, length-bounded slice of the original input\n * surfaced verbatim so reviewers can see the exact reason without scrolling\n * through long strings. The detector never embeds the snippet in a redaction\n * token or a regex alternation that would re-enter user-controlled data.\n */\nexport interface SemanticSuspicionMatch {\n category: SemanticSuspicionCategory;\n /** Short rationale for the match, suitable for a validation issue message. */\n reason: string;\n /** Up to 64 chars of the matched substring, ASCII-collapsed. */\n matchedSnippet: string;\n}\n\n/** Maximum length of `matchedSnippet` echoed in validation issue messages. */\nconst MAX_SNIPPET_LENGTH = 64;\n\n/** Safety bound on inputs: never scan more than this many characters. */\nconst MAX_SCAN_LENGTH = 16_384;\n\n/**\n * Shell-metacharacter sequences. Plain words like `rm` or single `;` are\n * not flagged on their own — only sequences that combine an unsafe binary or\n * redirection with a metacharacter are flagged, so benign sentences like\n * \"remove the file from the cart\" do not trip.\n *\n * Reasoning per pattern (regex source omitted from JSDoc to keep the\n * comment block parseable; see the `RegExp` literals below):\n * - rm -rf path : the canonical destructive POSIX call\n * - fork-bomb prefix : the leading bytes of `:(){ :|: };:`\n * - pipe-to-shell : `| sh` / `| bash`\n * - device exfil : output redirection into `/dev/null` or `/dev/tcp/`\n * - mkfifo / nc -l : reverse-shell building blocks\n */\nconst SHELL_METACHARACTER_PATTERNS: readonly RegExp[] = [\n /\\brm\\s+-rf?\\s+\\/[^\\s]*/iu,\n /:\\(\\)\\s*\\{[^}]*\\|/u,\n /\\|\\s*(?:ba)?sh\\b/iu,\n />\\s*\\/dev\\/(?:null|tcp\\/)/iu,\n /\\bmkfifo\\b/iu,\n /\\bnc\\s+-l\\b/iu,\n];\n\n/**\n * Command substitution / backtick-eval shapes. These only apply when a\n * shell would interpret them as a sub-shell call, so `$(value)` written\n * inside a literal natural-language sentence still trips — that is\n * intended, because a generated step like \"wait for `$(get_token)`\" would\n * leak a sub-shell call into a downstream tooling artifact.\n */\nconst COMMAND_SUBSTITUTION_PATTERNS: readonly RegExp[] = [\n /\\$\\([^)]+\\)/u,\n /`[^`\\n]+`/u,\n /\\$\\{IFS\\}/iu,\n];\n\n/**\n * JNDI / log4shell-style payloads. The canonical `${jndi:` prefix and its\n * obfuscation variants are unambiguously hostile. These belong to no\n * generated test step, period.\n */\nconst JNDI_LOG4SHELL_PATTERNS: readonly RegExp[] = [\n /\\$\\{\\s*(?:jndi|j\\$\\{[^}]*\\}ndi|\\$\\{lower:j\\}ndi)\\s*:/iu,\n /\\$\\{\\s*[\\w:.]*?:-j[^}]*\\}/iu,\n];\n\n/**\n * Long base64 runs. A run of base64-alphabet characters of length >= 64\n * with at least one digit and at least one uppercase letter is treated as\n * \"likely encoded payload\". Plain English words rarely satisfy this.\n */\nconst BASE64_RUN_RE = /[A-Za-z0-9+/]{64,}={0,2}/u;\n\n/**\n * Long hex runs. >= 40 hex characters (SHA-1 length) with no whitespace.\n * Sentence text never carries unbroken hex of this length.\n */\nconst HEX_RUN_RE = /\\b[0-9a-fA-F]{40,}\\b/u;\n\n/**\n * `<script>` tags and inline event handlers. The XSS shapes never belong\n * in a QC test step / expected-result string. Detection is HTML-aware\n * enough to catch attribute-shaped event handlers like `onclick=alert(1)`\n * and the case-insensitive `<sCrIpT>` variant.\n */\nconst SCRIPT_TAG_RE = /<\\s*script\\b[^>]*>/iu;\nconst HTML_EVENT_HANDLER_RE =\n /\\bon[a-z]{3,20}\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s>]+)/iu;\n\n/**\n * Dangerous URL schemes inside step text. `javascript:`, `data:`, and\n * `vbscript:` are never legitimate inside a QC step description. The\n * lookahead `(?=[A-Za-z0-9/+_\\-])` requires actual URL content directly\n * after the colon so labels like \"Test data: <value>\" or \"Order data:\"\n * (label + space) do not trip — only well-formed URL-scheme starts do.\n */\nconst DANGEROUS_URL_SCHEME_RE =\n /\\b(?:javascript|data|vbscript):(?=[A-Za-z0-9/+_-])/iu;\n\n/**\n * Short-circuit guard. Empty strings cannot match. Strings longer than\n * `MAX_SCAN_LENGTH` are clipped before the scan to bound regex worst-case.\n */\nconst prepareInput = (input: string): string | null => {\n if (input.length === 0) return null;\n return input.length > MAX_SCAN_LENGTH\n ? input.slice(0, MAX_SCAN_LENGTH)\n : input;\n};\n\nconst buildSnippet = (\n source: string,\n match: RegExpExecArray | null,\n): string => {\n if (match === null) {\n return source.slice(0, MAX_SNIPPET_LENGTH);\n }\n return match[0].slice(0, MAX_SNIPPET_LENGTH);\n};\n\nconst tryPatterns = (\n source: string,\n patterns: readonly RegExp[],\n category: SemanticSuspicionCategory,\n reason: string,\n): SemanticSuspicionMatch | null => {\n for (const pattern of patterns) {\n const match = pattern.exec(source);\n if (match !== null) {\n return { category, reason, matchedSnippet: buildSnippet(source, match) };\n }\n }\n return null;\n};\n\nconst tryPattern = (\n source: string,\n pattern: RegExp,\n category: SemanticSuspicionCategory,\n reason: string,\n): SemanticSuspicionMatch | null => {\n const match = pattern.exec(source);\n if (match === null) return null;\n return { category, reason, matchedSnippet: buildSnippet(source, match) };\n};\n\n/**\n * Run all deny-list categories against `input`. Returns the first match\n * encountered in declaration order, or `null` if nothing matches. The\n * declaration order is meaningful: the most specific / most dangerous\n * categories run first so that a JNDI payload buried inside a base64-shape\n * blob is still reported as JNDI.\n *\n * Behavior on edge cases:\n * - empty string → `null`\n * - whitespace-only string → `null`\n * - input longer than `MAX_SCAN_LENGTH` is clipped to that length\n * - the returned `matchedSnippet` is at most `MAX_SNIPPET_LENGTH` chars\n */\nexport const detectSuspiciousContent = (\n input: string,\n): SemanticSuspicionMatch | null => {\n const source = prepareInput(input);\n if (source === null) return null;\n if (source.trim().length === 0) return null;\n\n const jndi = tryPatterns(\n source,\n JNDI_LOG4SHELL_PATTERNS,\n \"jndi_log4shell\",\n \"matches JNDI / log4shell payload shape\",\n );\n if (jndi !== null) return jndi;\n\n const script = tryPattern(\n source,\n SCRIPT_TAG_RE,\n \"script_tag\",\n \"contains <script> tag shape\",\n );\n if (script !== null) return script;\n\n const handler = tryPattern(\n source,\n HTML_EVENT_HANDLER_RE,\n \"html_event_handler\",\n \"contains inline HTML event-handler attribute\",\n );\n if (handler !== null) return handler;\n\n const url = tryPattern(\n source,\n DANGEROUS_URL_SCHEME_RE,\n \"dangerous_url_scheme\",\n \"contains javascript:/data:/vbscript: URL scheme\",\n );\n if (url !== null) return url;\n\n const shell = tryPatterns(\n source,\n SHELL_METACHARACTER_PATTERNS,\n \"shell_metacharacters\",\n \"matches destructive shell-command shape\",\n );\n if (shell !== null) return shell;\n\n const subst = tryPatterns(\n source,\n COMMAND_SUBSTITUTION_PATTERNS,\n \"command_substitution\",\n \"contains command-substitution shape ($(...) / backticks / ${IFS})\",\n );\n if (subst !== null) return subst;\n\n const hex = tryPattern(\n source,\n HEX_RUN_RE,\n \"encoded_payload_hex\",\n \"contains long hex run (>= 40 chars; likely encoded payload)\",\n );\n if (hex !== null) return hex;\n\n const base64 = tryPattern(\n source,\n BASE64_RUN_RE,\n \"encoded_payload_base64\",\n \"contains long base64 run (>= 64 chars; likely encoded payload)\",\n );\n if (base64 !== null) return base64;\n\n return null;\n};\n\n// ---------------------------------------------------------------------------\n// Reviewer overrides\n// ---------------------------------------------------------------------------\n\n/**\n * Stable string used both as the inbound transition `kind` and as the\n * `overrideKind` metadata value identifying a semantic-content override\n * note inside the persisted review-event log. Stable across versions to\n * keep historical events parseable.\n */\nexport const SEMANTIC_CONTENT_OVERRIDE_NOTE_KIND = \"note\" as const;\n\n/** Metadata key marking a `note` event as a semantic-content override. */\nexport const SEMANTIC_CONTENT_OVERRIDE_METADATA_KIND_KEY =\n \"overrideKind\" as const;\n\n/** Metadata key carrying the validation issue path being overridden. */\nexport const SEMANTIC_CONTENT_OVERRIDE_METADATA_PATH_KEY =\n \"overridePath\" as const;\n\n/** Metadata key carrying the deny-list category being overridden. */\nexport const SEMANTIC_CONTENT_OVERRIDE_METADATA_CATEGORY_KEY =\n \"overrideCategory\" as const;\n\n/** Metadata key carrying the reviewer-supplied justification text. */\nexport const SEMANTIC_CONTENT_OVERRIDE_METADATA_JUSTIFICATION_KEY =\n \"overrideJustification\" as const;\n\n/**\n * Sentinel value stamped on `overrideKind` for semantic-content overrides.\n * Distinct from validation/policy outcome literals so a future override\n * type cannot collide with this one by accident.\n */\nexport const SEMANTIC_CONTENT_OVERRIDE_KIND_VALUE =\n \"semantic_suspicious_content\" as const;\n\n/** Maximum justification length accepted by `recordSemanticContentOverride`. */\nexport const SEMANTIC_CONTENT_OVERRIDE_MAX_JUSTIFICATION_LENGTH = 512;\n\nexport type PrincipalRef = string;\nexport type ISO8601 = string;\n\nexport const SEMANTIC_CONTENT_OVERRIDE_HMAC_ALGORITHM = \"hmac-sha256\" as const;\n\nexport interface HmacBlock {\n algorithm: typeof SEMANTIC_CONTENT_OVERRIDE_HMAC_ALGORITHM;\n digest: string;\n keyId?: string;\n}\n\nexport type OverrideAuthoritySecretProvider = string | (() => string);\n\nexport interface OverrideAuthorityProvider {\n /** Operator-managed verification secret. Never persisted. */\n hmacSecret: OverrideAuthoritySecretProvider;\n /** Optional key identifier stamped into signed entries. */\n keyId?: string;\n /** Optional deterministic clock for tests. Defaults to `Date.now`. */\n now?: () => number;\n}\n\nexport interface SemanticContentOverrideEntry {\n category: SemanticSuspicionCategory;\n justification: string;\n actor: PrincipalRef;\n signedAt: ISO8601;\n signature: HmacBlock;\n expiresAt?: ISO8601;\n verifiedSignature: boolean;\n}\n\nexport interface CreateSignedSemanticContentOverrideEntryInput {\n jobId: string;\n testCaseId: string;\n path: string;\n category: SemanticSuspicionCategory;\n justification: string;\n actor: PrincipalRef;\n signedAt: ISO8601;\n expiresAt?: ISO8601;\n authority: OverrideAuthorityProvider;\n}\n\n/**\n * Active overrides for one job. Entries are keyed by `testCaseId → path`,\n * with each value carrying the reviewer identity plus the signed audit data\n * needed to verify the override before policy-gate may honor it.\n */\nexport type SemanticContentOverrideMap = ReadonlyMap<\n string,\n ReadonlyMap<string, SemanticContentOverrideEntry>\n>;\n\nconst categoryFromValidationMessage = (\n message: string,\n): SemanticSuspicionCategory | undefined => {\n const separatorIndex = message.indexOf(\":\");\n if (separatorIndex <= 0) return undefined;\n const category = message.slice(0, separatorIndex);\n return isSemanticSuspicionCategory(category) ? category : undefined;\n};\n\nconst issueHasSemanticContentOverride = (\n issue: TestCaseValidationReport[\"issues\"][number],\n overrides: SemanticContentOverrideMap,\n): boolean => {\n if (issue.testCaseId === undefined) return false;\n const paths = overrides.get(issue.testCaseId);\n if (paths === undefined) return false;\n const entry = paths.get(issue.path);\n if (entry === undefined) return false;\n const category = categoryFromValidationMessage(issue.message);\n if (category === undefined) return false;\n return entry.category === category;\n};\n\n/**\n * Audit-friendly view of a single override extracted from the event log.\n * One entry per `(testCaseId, path)` pair.\n */\nexport interface SemanticContentOverride {\n testCaseId: string;\n path: string;\n category: SemanticSuspicionCategory;\n justification: string;\n actor: PrincipalRef;\n signedAt: ISO8601;\n expiresAt?: ISO8601;\n signature: HmacBlock;\n verifiedSignature: boolean;\n eventId: string;\n}\n\n/** Inputs for `recordSemanticContentOverride`. */\nexport interface SemanticContentOverrideInput {\n jobId: string;\n testCaseId: string;\n /** JSON-pointer-style path of the validation issue being overridden. */\n path: string;\n /** Deny-list category being overridden — must match the original finding. */\n category: SemanticSuspicionCategory;\n /** Identity of the reviewer recording the override. */\n actor: PrincipalRef;\n /** Non-empty rationale; persisted into `note` and `metadata`. */\n justification: string;\n /** ISO-8601 timestamp for the resulting review event. */\n at: ISO8601;\n /** Optional override expiry. Expired entries are rejected fail-closed. */\n expiresAt?: ISO8601;\n /** Caller-supplied authority provider used to sign the entry. */\n authority: OverrideAuthorityProvider;\n}\n\n/** Refusal codes raised by `recordSemanticContentOverride` BEFORE the store call. */\nexport type RecordSemanticContentOverrideRefusalCode =\n | \"actor_required\"\n | \"justification_required\"\n | \"justification_too_long\"\n | \"path_required\"\n | \"test_case_id_required\"\n | \"category_unknown\"\n | \"override_authority_required\";\n\nexport const SEMANTIC_CONTENT_OVERRIDE_METADATA_SIGNED_AT_KEY =\n \"overrideSignedAt\" as const;\n\nexport const SEMANTIC_CONTENT_OVERRIDE_METADATA_SIGNATURE_KEY =\n \"overrideSignature\" as const;\n\nexport const SEMANTIC_CONTENT_OVERRIDE_METADATA_SIGNATURE_KEY_ID_KEY =\n \"overrideSignatureKeyId\" as const;\n\nexport const SEMANTIC_CONTENT_OVERRIDE_METADATA_EXPIRES_AT_KEY =\n \"overrideExpiresAt\" as const;\n\nexport const SEMANTIC_CONTENT_OVERRIDE_METADATA_VERIFIED_SIGNATURE_KEY =\n \"verifiedSignature\" as const;\n\nexport interface InvalidSemanticContentOverride {\n testCaseId: string;\n path: string;\n reason: string;\n}\n\nexport type InvalidSemanticContentOverrideMap = ReadonlyMap<\n string,\n ReadonlyMap<string, string>\n>;\n\nconst SEMANTIC_SUSPICION_CATEGORY_SET: ReadonlySet<SemanticSuspicionCategory> =\n new Set(SEMANTIC_SUSPICION_CATEGORIES);\n\nconst isSemanticSuspicionCategory = (\n value: unknown,\n): value is SemanticSuspicionCategory =>\n typeof value === \"string\" &&\n SEMANTIC_SUSPICION_CATEGORY_SET.has(value as SemanticSuspicionCategory);\n\nconst HMAC_DIGEST_RE = /^[0-9a-f]{64}$/u;\n\nconst resolveOverrideAuthoritySecret = (\n provider: OverrideAuthorityProvider,\n): string => {\n const secret =\n typeof provider.hmacSecret === \"function\"\n ? provider.hmacSecret()\n : provider.hmacSecret;\n if (typeof secret !== \"string\" || secret.length === 0) {\n throw new RangeError(\n \"semantic-content override authority must resolve to a non-empty string\",\n );\n }\n return secret;\n};\n\nconst buildOverrideSignaturePayload = (input: {\n jobId: string;\n testCaseId: string;\n path: string;\n category: SemanticSuspicionCategory;\n justification: string;\n actor: PrincipalRef;\n signedAt: ISO8601;\n expiresAt?: ISO8601;\n}): string => {\n return canonicalJson({\n overrideKind: SEMANTIC_CONTENT_OVERRIDE_KIND_VALUE,\n jobId: input.jobId,\n testCaseId: input.testCaseId,\n path: input.path,\n category: input.category,\n justification: input.justification,\n actor: input.actor,\n signedAt: input.signedAt,\n expiresAt: input.expiresAt ?? null,\n });\n};\n\nconst signOverrideSignaturePayload = (\n payload: string,\n authority: OverrideAuthorityProvider,\n): HmacBlock => {\n const secret = resolveOverrideAuthoritySecret(authority);\n return {\n algorithm: SEMANTIC_CONTENT_OVERRIDE_HMAC_ALGORITHM,\n digest: createHmac(\"sha256\", secret).update(payload).digest(\"hex\"),\n ...(authority.keyId !== undefined ? { keyId: authority.keyId } : {}),\n };\n};\n\nconst signaturesMatch = (expected: string, candidate: string): boolean => {\n if (!HMAC_DIGEST_RE.test(expected) || !HMAC_DIGEST_RE.test(candidate)) {\n return false;\n }\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(candidate, \"hex\"),\n );\n};\n\nconst buildOverrideEntryReason = (\n entry: SemanticContentOverrideEntry,\n): string | null => {\n if (entry.actor.length === 0) {\n return \"override entry is missing actor\";\n }\n if (entry.signedAt.length === 0) {\n return \"override entry is missing signedAt\";\n }\n if (entry.justification.trim().length === 0) {\n return \"override entry is missing justification\";\n }\n if (!isSemanticSuspicionCategory(entry.category)) {\n return \"override entry has unknown category\";\n }\n if (!HMAC_DIGEST_RE.test(entry.signature.digest)) {\n return \"override entry is missing signature\";\n }\n if (!entry.verifiedSignature) {\n return \"override entry is missing verifiedSignature audit stamp\";\n }\n if (entry.expiresAt !== undefined) {\n const expiresAtMs = Date.parse(entry.expiresAt);\n if (!Number.isFinite(expiresAtMs)) {\n return \"override entry has invalid expiresAt\";\n }\n }\n return null;\n};\n\nconst verifyOverrideEntry = (\n input: {\n jobId: string;\n testCaseId: string;\n path: string;\n entry: SemanticContentOverrideEntry;\n },\n authority: OverrideAuthorityProvider | undefined,\n): string | null => {\n const structuralReason = buildOverrideEntryReason(input.entry);\n if (structuralReason !== null) {\n return structuralReason;\n }\n if (authority === undefined) {\n return \"override authority provider missing\";\n }\n if (input.entry.expiresAt !== undefined) {\n const nowMs = (authority.now ?? Date.now)();\n if (Date.parse(input.entry.expiresAt) <= nowMs) {\n return \"override entry has expired\";\n }\n }\n const payload = buildOverrideSignaturePayload({\n jobId: input.jobId,\n testCaseId: input.testCaseId,\n path: input.path,\n category: input.entry.category,\n justification: input.entry.justification,\n actor: input.entry.actor,\n signedAt: input.entry.signedAt,\n ...(input.entry.expiresAt !== undefined\n ? { expiresAt: input.entry.expiresAt }\n : {}),\n });\n const expected = signOverrideSignaturePayload(payload, authority);\n return signaturesMatch(expected.digest, input.entry.signature.digest)\n ? null\n : \"override signature failed verification\";\n};\n\nconst setOverrideEntry = (\n target: Map<string, Map<string, SemanticContentOverrideEntry>>,\n testCaseId: string,\n path: string,\n entry: SemanticContentOverrideEntry,\n): void => {\n let paths = target.get(testCaseId);\n if (paths === undefined) {\n paths = new Map<string, SemanticContentOverrideEntry>();\n target.set(testCaseId, paths);\n }\n paths.set(path, entry);\n};\n\nconst setInvalidOverrideReason = (\n target: Map<string, Map<string, string>>,\n testCaseId: string,\n path: string,\n reason: string,\n): void => {\n let paths = target.get(testCaseId);\n if (paths === undefined) {\n paths = new Map<string, string>();\n target.set(testCaseId, paths);\n }\n paths.set(path, reason);\n};\n\nexport const createSignedSemanticContentOverrideEntry = (\n input: CreateSignedSemanticContentOverrideEntryInput,\n): SemanticContentOverrideEntry => {\n const payload = buildOverrideSignaturePayload({\n jobId: input.jobId,\n testCaseId: input.testCaseId,\n path: input.path,\n category: input.category,\n justification: input.justification,\n actor: input.actor,\n signedAt: input.signedAt,\n ...(input.expiresAt !== undefined ? { expiresAt: input.expiresAt } : {}),\n });\n const signature = signOverrideSignaturePayload(payload, input.authority);\n return {\n category: input.category,\n justification: input.justification,\n actor: input.actor,\n signedAt: input.signedAt,\n signature,\n ...(input.expiresAt !== undefined ? { expiresAt: input.expiresAt } : {}),\n verifiedSignature: true,\n };\n};\n\n/**\n * Record a structured semantic-content override note via the review store.\n * Validates inputs before touching the store so callers see a clear\n * structured refusal instead of a generic store-level \"kind unknown\".\n */\nexport const recordSemanticContentOverride = async (\n store: ReviewStore,\n input: SemanticContentOverrideInput,\n): Promise<\n | RecordTransitionResult\n | { ok: false; code: RecordSemanticContentOverrideRefusalCode }\n> => {\n if (input.testCaseId.length === 0) {\n return { ok: false, code: \"test_case_id_required\" };\n }\n if (input.path.length === 0) {\n return { ok: false, code: \"path_required\" };\n }\n if (input.actor.length === 0) {\n return { ok: false, code: \"actor_required\" };\n }\n const trimmed = input.justification.trim();\n if (trimmed.length === 0) {\n return { ok: false, code: \"justification_required\" };\n }\n if (\n input.justification.length >\n SEMANTIC_CONTENT_OVERRIDE_MAX_JUSTIFICATION_LENGTH\n ) {\n return { ok: false, code: \"justification_too_long\" };\n }\n if (!SEMANTIC_SUSPICION_CATEGORY_SET.has(input.category)) {\n return { ok: false, code: \"category_unknown\" };\n }\n const entry = createSignedSemanticContentOverrideEntry({\n jobId: input.jobId,\n testCaseId: input.testCaseId,\n path: input.path,\n category: input.category,\n justification: input.justification,\n actor: input.actor,\n signedAt: input.at,\n ...(input.expiresAt !== undefined ? { expiresAt: input.expiresAt } : {}),\n authority: input.authority,\n });\n\n const transition: RecordTransitionInput = {\n jobId: input.jobId,\n testCaseId: input.testCaseId,\n kind: SEMANTIC_CONTENT_OVERRIDE_NOTE_KIND,\n at: input.at,\n actor: input.actor,\n note: input.justification,\n metadata: {\n [SEMANTIC_CONTENT_OVERRIDE_METADATA_KIND_KEY]:\n SEMANTIC_CONTENT_OVERRIDE_KIND_VALUE,\n [SEMANTIC_CONTENT_OVERRIDE_METADATA_PATH_KEY]: input.path,\n [SEMANTIC_CONTENT_OVERRIDE_METADATA_CATEGORY_KEY]: entry.category,\n [SEMANTIC_CONTENT_OVERRIDE_METADATA_JUSTIFICATION_KEY]:\n entry.justification,\n [SEMANTIC_CONTENT_OVERRIDE_METADATA_SIGNED_AT_KEY]: entry.signedAt,\n [SEMANTIC_CONTENT_OVERRIDE_METADATA_SIGNATURE_KEY]:\n entry.signature.digest,\n [SEMANTIC_CONTENT_OVERRIDE_METADATA_VERIFIED_SIGNATURE_KEY]:\n entry.verifiedSignature,\n ...(entry.signature.keyId !== undefined\n ? {\n [SEMANTIC_CONTENT_OVERRIDE_METADATA_SIGNATURE_KEY_ID_KEY]:\n entry.signature.keyId,\n }\n : {}),\n ...(entry.expiresAt !== undefined\n ? {\n [SEMANTIC_CONTENT_OVERRIDE_METADATA_EXPIRES_AT_KEY]:\n entry.expiresAt,\n }\n : {}),\n },\n };\n return store.recordTransition(transition);\n};\n\nconst isSemanticContentOverrideEvent = (\n event: ReviewEvent,\n): event is ReviewEvent & { testCaseId: string } => {\n if (event.kind !== \"note\") return false;\n if (typeof event.testCaseId !== \"string\") return false;\n if (event.metadata === undefined) return false;\n const kind = event.metadata[SEMANTIC_CONTENT_OVERRIDE_METADATA_KIND_KEY];\n if (kind !== SEMANTIC_CONTENT_OVERRIDE_KIND_VALUE) return false;\n const path = event.metadata[SEMANTIC_CONTENT_OVERRIDE_METADATA_PATH_KEY];\n return typeof path === \"string\" && path.length > 0;\n};\n\n/**\n * Reduce a persisted review-event log to the set of currently active\n * semantic-content overrides. Replay-safe: re-running over the same log\n * yields a byte-identical map. Later events take precedence over earlier\n * ones for the same `(testCaseId, path)` pair so a corrected override\n * (e.g., after a re-edit) supersedes the earlier one.\n *\n * The returned map keys (`testCaseId` strings) and value sets (`path`\n * strings) are sorted in iteration order so downstream byte-stable\n * persistence remains deterministic.\n */\nexport const extractSemanticContentOverrides = (\n events: readonly ReviewEvent[],\n): SemanticContentOverrideMap => {\n const latestByKey = new Map<string, SemanticContentOverride>();\n for (const event of events) {\n if (!isSemanticContentOverrideEvent(event)) continue;\n const path = event.metadata?.[\n SEMANTIC_CONTENT_OVERRIDE_METADATA_PATH_KEY\n ] as string;\n const rawCategory =\n event.metadata?.[SEMANTIC_CONTENT_OVERRIDE_METADATA_CATEGORY_KEY];\n if (!isSemanticSuspicionCategory(rawCategory)) continue;\n const rawJustification =\n event.metadata?.[SEMANTIC_CONTENT_OVERRIDE_METADATA_JUSTIFICATION_KEY];\n if (typeof rawJustification !== \"string\") continue;\n const rawSignedAt =\n event.metadata?.[SEMANTIC_CONTENT_OVERRIDE_METADATA_SIGNED_AT_KEY];\n const rawSignature =\n event.metadata?.[SEMANTIC_CONTENT_OVERRIDE_METADATA_SIGNATURE_KEY];\n const rawKeyId =\n event.metadata?.[SEMANTIC_CONTENT_OVERRIDE_METADATA_SIGNATURE_KEY_ID_KEY];\n const rawExpiresAt =\n event.metadata?.[SEMANTIC_CONTENT_OVERRIDE_METADATA_EXPIRES_AT_KEY];\n const rawVerified =\n event.metadata?.[\n SEMANTIC_CONTENT_OVERRIDE_METADATA_VERIFIED_SIGNATURE_KEY\n ];\n const key = `${event.testCaseId}\\u0000${path}`;\n latestByKey.set(key, {\n testCaseId: event.testCaseId,\n path,\n category: rawCategory,\n justification: rawJustification,\n actor: typeof event.actor === \"string\" ? event.actor : \"\",\n signedAt: typeof rawSignedAt === \"string\" ? rawSignedAt : \"\",\n signature: {\n algorithm: SEMANTIC_CONTENT_OVERRIDE_HMAC_ALGORITHM,\n digest: typeof rawSignature === \"string\" ? rawSignature : \"\",\n ...(typeof rawKeyId === \"string\" ? { keyId: rawKeyId } : {}),\n },\n ...(typeof rawExpiresAt === \"string\" ? { expiresAt: rawExpiresAt } : {}),\n verifiedSignature: rawVerified === true,\n eventId: event.id,\n });\n }\n const grouped = new Map<string, Map<string, SemanticContentOverrideEntry>>();\n const sorted = [...latestByKey.values()].sort((a, b) => {\n if (a.testCaseId !== b.testCaseId) {\n return a.testCaseId.localeCompare(b.testCaseId);\n }\n return a.path.localeCompare(b.path);\n });\n for (const override of sorted) {\n let paths = grouped.get(override.testCaseId);\n if (paths === undefined) {\n paths = new Map<string, SemanticContentOverrideEntry>();\n grouped.set(override.testCaseId, paths);\n }\n paths.set(override.path, {\n category: override.category,\n justification: override.justification,\n actor: override.actor,\n signedAt: override.signedAt,\n signature: override.signature,\n ...(override.expiresAt !== undefined\n ? { expiresAt: override.expiresAt }\n : {}),\n verifiedSignature: override.verifiedSignature,\n });\n }\n return grouped;\n};\n\n/**\n * Audit-friendly list view of the same override data. Sorted by\n * `(testCaseId, path)` for byte-stable rendering.\n */\nexport const listSemanticContentOverrides = (\n events: readonly ReviewEvent[],\n): readonly SemanticContentOverride[] => {\n const latestByKey = new Map<string, SemanticContentOverride>();\n for (const event of events) {\n if (!isSemanticContentOverrideEvent(event)) continue;\n const path = event.metadata?.[\n SEMANTIC_CONTENT_OVERRIDE_METADATA_PATH_KEY\n ] as string;\n const rawCategory =\n event.metadata?.[SEMANTIC_CONTENT_OVERRIDE_METADATA_CATEGORY_KEY];\n if (!isSemanticSuspicionCategory(rawCategory)) continue;\n const rawJustification =\n event.metadata?.[SEMANTIC_CONTENT_OVERRIDE_METADATA_JUSTIFICATION_KEY];\n if (typeof rawJustification !== \"string\") continue;\n const rawSignedAt =\n event.metadata?.[SEMANTIC_CONTENT_OVERRIDE_METADATA_SIGNED_AT_KEY];\n const rawSignature =\n event.metadata?.[SEMANTIC_CONTENT_OVERRIDE_METADATA_SIGNATURE_KEY];\n const rawKeyId =\n event.metadata?.[SEMANTIC_CONTENT_OVERRIDE_METADATA_SIGNATURE_KEY_ID_KEY];\n const rawExpiresAt =\n event.metadata?.[SEMANTIC_CONTENT_OVERRIDE_METADATA_EXPIRES_AT_KEY];\n const rawVerified =\n event.metadata?.[\n SEMANTIC_CONTENT_OVERRIDE_METADATA_VERIFIED_SIGNATURE_KEY\n ];\n const key = `${event.testCaseId}\\u0000${path}`;\n latestByKey.set(key, {\n testCaseId: event.testCaseId,\n path,\n category: rawCategory,\n justification: rawJustification,\n actor: typeof event.actor === \"string\" ? event.actor : \"\",\n signedAt: typeof rawSignedAt === \"string\" ? rawSignedAt : \"\",\n signature: {\n algorithm: SEMANTIC_CONTENT_OVERRIDE_HMAC_ALGORITHM,\n digest: typeof rawSignature === \"string\" ? rawSignature : \"\",\n ...(typeof rawKeyId === \"string\" ? { keyId: rawKeyId } : {}),\n },\n ...(typeof rawExpiresAt === \"string\" ? { expiresAt: rawExpiresAt } : {}),\n verifiedSignature: rawVerified === true,\n eventId: event.id,\n });\n }\n return [...latestByKey.values()].sort((a, b) => {\n if (a.testCaseId !== b.testCaseId) {\n return a.testCaseId.localeCompare(b.testCaseId);\n }\n return a.path.localeCompare(b.path);\n });\n};\n\n/**\n * Compute the post-override `blocked` flag from a validation report. Pure;\n * does not mutate the report. The original report still records the\n * `error`-severity finding for audit, but downstream gates use this\n * helper to know whether the case should still block.\n *\n * Returns `true` when the report has any `error` issue that is NOT a\n * `semantic_suspicious_content` issue covered by the override map.\n */\nexport const effectiveSemanticContentBlock = (\n validation: TestCaseValidationReport,\n overrides: SemanticContentOverrideMap,\n): boolean => {\n for (const issue of validation.issues) {\n if (issue.severity !== \"error\") continue;\n if (issue.code !== \"semantic_suspicious_content\") return true;\n if (!issueHasSemanticContentOverride(issue, overrides)) {\n return true;\n }\n }\n return false;\n};\n\n/**\n * Join an override map against the current validation report, keeping only\n * overrides that target an actual `semantic_suspicious_content` issue. This\n * prevents stale, misspelled, or future-path notes from influencing downstream\n * gates while preserving the simple `testCaseId -> paths` map shape.\n */\nexport const filterSemanticContentOverridesForValidation = (\n validation: TestCaseValidationReport,\n overrides: SemanticContentOverrideMap,\n): SemanticContentOverrideMap => {\n return partitionSemanticContentOverridesForValidation(\n validation,\n overrides,\n undefined,\n ).valid;\n};\n\nexport const partitionSemanticContentOverridesForValidation = (\n validation: TestCaseValidationReport,\n overrides: SemanticContentOverrideMap,\n authority: OverrideAuthorityProvider | undefined,\n): {\n valid: SemanticContentOverrideMap;\n invalid: InvalidSemanticContentOverrideMap;\n} => {\n const allowed = new Map<string, Map<string, SemanticContentOverrideEntry>>();\n const invalid = new Map<string, Map<string, string>>();\n\n for (const issue of validation.issues) {\n if (issue.code !== \"semantic_suspicious_content\") continue;\n const testCaseId = issue.testCaseId;\n if (testCaseId === undefined) continue;\n const paths = overrides.get(testCaseId);\n if (paths === undefined) continue;\n const entry = paths.get(issue.path);\n if (entry === undefined) continue;\n const category = categoryFromValidationMessage(issue.message);\n if (category === undefined || entry.category !== category) {\n continue;\n }\n const reason = verifyOverrideEntry(\n {\n jobId: validation.jobId,\n testCaseId,\n path: issue.path,\n entry,\n },\n authority,\n );\n if (reason !== null) {\n setInvalidOverrideReason(invalid, testCaseId, issue.path, reason);\n continue;\n }\n setOverrideEntry(allowed, testCaseId, issue.path, entry);\n }\n\n const sortPathMap = <T>(paths: Map<string, T>): Map<string, T> =>\n new Map([...paths.entries()].sort(([a], [b]) => a.localeCompare(b)));\n const sortOuterMap = <T>(\n value: Map<string, Map<string, T>>,\n ): Map<string, Map<string, T>> =>\n new Map(\n [...value.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([testCaseId, paths]) => [testCaseId, sortPathMap(paths)]),\n );\n\n return {\n valid: sortOuterMap(allowed),\n invalid: sortOuterMap(invalid),\n };\n};\n","/**\n * Test Intelligence — public contracts for autonomous REST + deterministic generation.\n *\n * These types define the public API surface for Test Intelligence consumers.\n * They must not import from internal services.\n *\n * Contract version: 4.64.0\n * See CONTRACT_CHANGELOG.md for contract change history and VERSIONING.md for\n * package-versus-contract versioning policy.\n */\n\nexport {\n assertRoleLineageDepth,\n isBrandedId,\n isRoleLineageDepth,\n MAX_ROLE_LINEAGE_DEPTH,\n toAgentRoleProfileId,\n toEvidenceArtifactId,\n toJobId,\n toLessonId,\n toRoleStepId,\n validateBrandedIdLabel,\n type AgentRoleProfileId,\n type EvidenceArtifactId,\n type JobId,\n type LessonId,\n type RoleStepId,\n} from \"./branded-ids.js\";\n\n/**\n * Runtime source-of-truth for allowed test-intelligence modes.\n *\n * Test intelligence is an opt-in, local-first feature with an isolated mode\n * namespace. Changes to this array must never affect legacy structured\n * generation mode contracts.\n */\nexport const ALLOWED_TEST_INTELLIGENCE_MODES = [\n \"deterministic_llm\",\n \"offline_eval\",\n] as const;\n\n/**\n * Bearer credential bound to a single review principal.\n *\n * Used by the test-intelligence review gate when four-eyes review is\n * enforced (#1376). The token authenticates the caller; the\n * `principalId` is the server-owned reviewer identity persisted on\n * review events and snapshots. Never reuse one token for multiple\n * principals.\n */\nexport interface TestIntelligenceReviewPrincipal {\n /** Opaque, non-secret reviewer principal id persisted in review audit logs. */\n principalId: string;\n /** Bearer token accepted for this principal's review-gate write requests. */\n bearerToken: string;\n}\n\n/**\n * Principal-bound credentials used by the controlled OpenText ALM API\n * transfer pipeline (#1372). The token authenticates the caller; the\n * `principalId` is the server-owned operator identity persisted in\n * `transfer-report.json` so audit lineage survives token rotation.\n * Never reuse one token for multiple principals.\n */\nexport interface TestIntelligenceTransferPrincipal {\n /** Opaque, non-secret operator principal id persisted in transfer audit logs. */\n principalId: string;\n /** Bearer token accepted for this principal's API transfer requests. */\n bearerToken: string;\n}\n\n/** Contract version for the opt-in test-intelligence surface. */\nexport const TEST_INTELLIGENCE_CONTRACT_VERSION = \"1.39.0\" as const;\n\n/**\n * Schema version for generated test case payloads.\n *\n * 1.1.0 — Issue #1735: optional additive field `regulatoryRelevance`\n * ({domain, rationale}) on each test case. Backwards compatible — the\n * validator accepts both 1.0.0-shaped lists (without the field) and\n * 1.1.0-shaped lists (with or without the field).\n *\n * 1.3.0 — Issue #2104: optional additive audit field\n * `audit.truncatedInstructionCount` records how many upstream repair\n * instructions were clipped before regeneration so validation can emit\n * a non-blocking audit warning.\n */\nexport const GENERATED_TEST_CASE_SCHEMA_VERSION = \"1.3.0\" as const;\n\n/** Persisted polarity labels for generated test cases (Issue #2030). */\nexport const ALLOWED_GENERATED_TEST_CASE_POLARITIES = [\n \"positive\",\n \"negative\",\n \"boundary\",\n \"validation\",\n \"navigation\",\n \"accessibility\",\n] as const;\n\n/** Machine-readable customer-eval rubric buckets for generated cases. */\nexport const ALLOWED_GENERATED_TEST_CASE_CATEGORIES = [\n \"positive_path\",\n \"negative_path\",\n \"boundary_value\",\n \"validation_rule\",\n \"navigation_flow\",\n \"accessibility\",\n] as const;\n\n/**\n * Prompt template version for the test-intelligence prompt family.\n *\n * Semver semantics (Issue #1943):\n * - PATCH — wording fixes that preserve token-byte-equivalence on the\n * baseline-fixture set (typo fixes, comment-only changes inside the\n * compiled prompt body, equivalent-byte rewordings).\n * - MINOR — additive sections, additional instructions, or new optional\n * directives that remain backwards-compatible with prior generator\n * outputs (the existing covered* arrays, figmaTraceRefs schema, and\n * evidence shape are unchanged).\n * - MAJOR — breaking section reordering, evidence-schema changes, or any\n * change that retires a directive contract previously relied on by a\n * downstream judge (Logic-Judge, Faithfulness-Judge, A11y-Judge).\n *\n * Every non-PATCH bump MUST be recorded in the relevant release evidence\n * with scope, motivation, and expected verdict-deltas on the baseline-fixture set.\n * The CI guard `scripts/check-prompt-template-version.mjs` (wired into\n * `pr-quality-gate.yml`) fails the build when `prompt-compiler.ts`\n * content changes but this constant is not bumped.\n *\n * History:\n *\n * 1.2.0 — Issue #1905: explicit form-screen accessibility directive added\n * to the generator system prompt and user-prompt preamble. Bump forces a\n * replay-cache miss for cached generator outputs that pre-date the new\n * directive so the cache cannot serve a stale list that lacks an a11y case\n * for a form screen.\n *\n * 1.3.0 — Issue #1941: dedicated `[5] CustomerDomainContext` section\n * promoted from custom-context-markdown so customer-supplied banking and\n * insurance rules become a first-class evidence source.\n *\n * 1.4.0 — Issue #1942: hard-gated technique-quota enforcement on the\n * generator user-prompt preamble (`GENERATOR_TECHNIQUE_QUOTA_RULE`).\n *\n * 1.4.1 — Issue #1987: unresolved validation rules now explicitly forbid\n * fabricated exact validation text, thresholds, min/max boundaries, and\n * blocked-submit behavior in the generator prompt preamble.\n *\n * 1.4.2 — Issue #1984: narrowed decorative-label filtering and hardened\n * logic-judge schema handling after prompt-compiler changes in the live-run\n * quality hardening flow.\n *\n * 1.4.3 — Issue #1986: financing-need domain-faithfulness rules now\n * instruct the generator to honor VAT-exclusion constraints and fall back\n * to open questions when exact arithmetic remains underspecified.\n */\nexport const TEST_INTELLIGENCE_PROMPT_TEMPLATE_VERSION = \"1.7.3\" as const;\n\n/** Visual sidecar schema version consumed by the prompt compiler (Issue #1386). */\nexport const VISUAL_SIDECAR_SCHEMA_VERSION = \"1.1.0\" as const;\n\n/**\n * Operator-supplied multimodal-sidecar deployment name (Issue #1959).\n *\n * The runner does not gate on a hard-coded list — the deployment name is\n * the verbatim Azure-AI-Foundry / gateway deployment id chosen by the\n * operator in `.env`. Validity is enforced at the gateway boundary\n * (HTTP 404 surfaces back to the policy gate); this type's contract is\n * limited to \"non-empty string of reasonable length\". Length is bounded\n * by the JSON-Schema validator at the visual-sidecar wire surface\n * (`{ minLength: 1, maxLength: 128 }`).\n *\n * The optional brand symbol documents intent without forcing a public\n * type-import migration on callers that pass plain string literals\n * (e.g., `llama-4-maverick-vision`,\n * `phi-4-multimodal-instruct`, `mistral-document-ai-2512`, `mock`).\n */\nexport type SidecarDeployment = string & {\n readonly __brand?: \"sidecar_deployment\";\n};\n\n/**\n * Maximum byte length accepted for an operator-supplied sidecar\n * deployment id at the wire-validation boundary. Mirrors Azure AI\n * Foundry's deployment-name length constraints.\n */\nexport const SIDECAR_DEPLOYMENT_MAX_LENGTH = 128 as const;\n\n/** Redaction policy bundle version applied before prompt compilation. */\nexport const REDACTION_POLICY_VERSION = \"1.0.0\" as const;\n\n/**\n * Version of the subprocessor register and its paired cross-border transfer\n * evidence. Stamped into every Wave 1 Validation evidence manifest so a\n * replay can verify which DORA Art. 28 / GDPR Ch. V control set was active\n * for the run.\n *\n * Bump rules - every material register or transfer-control change MUST bump\n * this version in the same PR.\n */\nexport const SUBPROCESSOR_REGISTER_VERSION = \"1.0.0\" as const;\n\n/**\n * Schema version for the machine-readable {@link SubprocessorRegister}\n * artifact emitted per run alongside `compliance-annotations.json`\n * (Issue #2174). Bumped on any breaking shape change to\n * {@link SubprocessorRegister}, {@link SubprocessorEntry}, or\n * {@link CrossBorderTransferEntry}.\n *\n * The schema version is independent of {@link SUBPROCESSOR_REGISTER_VERSION}:\n * the latter tracks the documented register content (subprocessor list +\n * cross-border ADR), this one tracks the artifact shape that auditors\n * cross-reference programmatically.\n */\nexport const SUBPROCESSOR_REGISTER_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Canonical filename for the machine-readable subprocessor register\n * artifact (Issue #2174). Persisted next to\n * `compliance-annotations.json` and `compliance-coverage-report.json`.\n */\nexport const SUBPROCESSOR_REGISTER_ARTIFACT_FILENAME =\n \"subprocessor-register.json\" as const;\n\n/**\n * Hosting regions that the subprocessor register may name as an\n * {@link SubprocessorEntry.hostingRegion} or as the source/destination of\n * a {@link CrossBorderTransferEntry}. The list mirrors the EEA Azure\n * regions recommended by the cross-border-transfer ADR plus the Mistral\n * `eu-west-1` family and a single explicit `operator-defined` entry for\n * categories where the operator selects the concrete region (Jira /\n * object-storage / hook hosts) rather than the package shipping a default.\n *\n * Bump rules — adding a region is a non-breaking change. Removing or\n * renaming one bumps {@link SUBPROCESSOR_REGISTER_SCHEMA_VERSION}.\n */\nexport const SUPPORTED_HOSTING_REGIONS = [\n \"westeurope\",\n \"northeurope\",\n \"francecentral\",\n \"germanywestcentral\",\n \"swedencentral\",\n \"eu-west-1\",\n \"operator-defined\",\n] as const;\n\n/** A single Azure / Mistral / operator-defined hosting region. */\nexport type SupportedHostingRegion = (typeof SUPPORTED_HOSTING_REGIONS)[number];\n\n/**\n * Region-attestation schema version (Issue #2177). Bumped on any breaking\n * change to the per-artifact region-attestation envelope or report shape.\n */\nexport const REGION_ATTESTATION_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Closed runtime list of operator-accepted hosting regions for cryptographic\n * per-artifact EU data-residency attestations (Issue #2177).\n */\nexport const SUPPORTED_REGION_ATTESTATION_HOSTING_REGIONS = [\n \"eu-central-1\",\n \"eu-west-1\",\n \"eu-west-3\",\n \"eu-north-1\",\n \"eu-south-1\",\n \"eu-de-1\",\n \"eu-fr-1\",\n \"switzerland-north\",\n \"norway-east\",\n] as const;\n\n/** One hosting region admitted by {@link RegionAttestation}. */\nexport type RegionAttestationHostingRegion =\n (typeof SUPPORTED_REGION_ATTESTATION_HOSTING_REGIONS)[number];\n\n/** One cryptographic per-artifact EU data-residency attestation. */\nexport interface RegionAttestation {\n readonly schemaVersion: typeof REGION_ATTESTATION_SCHEMA_VERSION;\n readonly artifactHash: string;\n readonly deploymentId: string;\n readonly servedFromRegion: RegionAttestationHostingRegion;\n readonly observedAtUtc: string;\n readonly attestedBy:\n | \"azure-instance-metadata\"\n | \"endpoint-cert-cn\"\n | \"operator-pinned\"\n /**\n * Issue #2187 — sovereign-cloud / air-gap deployment topologies\n * (STACKIT, T-Systems Open Sovereign Cloud, OVHcloud sovereign,\n * on-prem). These endpoints do not implement Azure IMDS and the\n * harness has no network reach to TLS-probe the upstream, so the\n * region claim is derived from the operator-signed sovereign-cloud\n * attestation source (e.g. signed deployment manifest baked into\n * the air-gapped image). Treated as a first-class attestation\n * source — equivalent strength to {@link `azure-instance-metadata`}\n * within its trust boundary — and is **not** flagged with\n * `severity: \"warning\"`.\n */\n | \"sovereign-cloud\";\n /**\n * Additive audit signal: present only when the attestation fell back to an\n * operator-pinned region because no stronger runtime evidence was available.\n */\n readonly severity?: \"warning\";\n readonly attestationSignatureHex: string;\n}\n\n/** Canonical filename for the per-run region-attestation report. */\nexport const REGION_ATTESTATION_REPORT_ARTIFACT_FILENAME =\n \"region-attestations.json\" as const;\n\n/** One artifact row inside the run-level region-attestation report. */\nexport interface RegionAttestationArtifactEntry {\n readonly filename: string;\n readonly artifactHash: string;\n readonly regionAttestations: readonly RegionAttestation[];\n}\n\n/** Machine-readable report of every artifact-level region attestation. */\nexport interface RegionAttestationReport {\n readonly schemaVersion: typeof REGION_ATTESTATION_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly jobId: string;\n readonly generatedAt: string;\n readonly artifacts: readonly RegionAttestationArtifactEntry[];\n readonly distinctRegions: readonly RegionAttestationHostingRegion[];\n}\n\n/**\n * A single ICT third-party / subprocessor entry under DORA Art. 28(3).\n * Structured form auditors cross-reference against\n * `compliance-annotations.json`.\n *\n * All fields are required so the artifact is byte-stable across runs;\n * optional fields are only added behind `?` when omission is the\n * meaningful signal (e.g. a subprocessor that has no SOC 2 report).\n */\nexport interface SubprocessorEntry {\n /** Stable, kebab-case identifier (e.g. `\"llm-gateway-text-generation\"`). */\n readonly subprocessorId: string;\n /** Contractual / legal name (operator-selected vendor where applicable). */\n readonly legalName: string;\n /** One-sentence purpose of the subprocessor in the test-intelligence pipeline. */\n readonly purpose: string;\n /** Hosting region from {@link SUPPORTED_HOSTING_REGIONS}. */\n readonly hostingRegion: SupportedHostingRegion;\n /** Sorted, deduplicated data-classification scope tokens. */\n readonly dataCategories: readonly string[];\n /** Sorted, deduplicated contractual safeguard citations (e.g. `\"DPA-2024\"`, `\"SCC-2021-Module-2\"`). */\n readonly contractualSafeguards: readonly string[];\n /** Optional SOC 2 Type II report reference (citation, not a URL). */\n readonly soc2ReportRef?: string;\n /** Optional ISO/IEC 27001 certificate reference (citation, not a URL). */\n readonly iso27001ReportRef?: string;\n /** Retention policy floor the operator must meet. */\n readonly retentionPolicy: string;\n /** ISO-8601 timestamp the entry was first added to the register. */\n readonly addedAt: string;\n}\n\n/**\n * A single cross-border transfer record under GDPR Ch. V. Each entry\n * declares the legal mechanism that legitimises a flow between two\n * regions in {@link SUPPORTED_HOSTING_REGIONS}. Even intra-EEA entries\n * are recorded for replay verifiability so an auditor can reconstruct\n * which transfer mechanism was active at a given run timestamp.\n */\nexport interface CrossBorderTransferEntry {\n /** Stable, kebab-case transfer identifier. */\n readonly transferId: string;\n /** Source region (where the data originates). */\n readonly sourceRegion: SupportedHostingRegion;\n /** Destination region (where the data flows to). */\n readonly destinationRegion: SupportedHostingRegion;\n /**\n * Legal mechanism. `\"adequacy-decision\"` covers intra-EEA and\n * Commission-recognised third-country adequacy decisions; `\"scc-2021\"`\n * is the 2021 EU Standard Contractual Clauses; `\"bcr\"` is Binding\n * Corporate Rules; `\"consent\"` is GDPR Art. 49(1)(a) (rare and\n * narrow); `\"other\"` is reserved for derogations the operator\n * documents in their own register.\n */\n readonly transferMechanism:\n | \"scc-2021\"\n | \"adequacy-decision\"\n | \"bcr\"\n | \"consent\"\n | \"other\";\n /** Free-form citation pointing to the contractual / legal artefact. */\n readonly mechanismCitation: string;\n /** One-sentence purpose of the transfer. */\n readonly purpose: string;\n /** ISO-8601 timestamp the transfer was approved by the operator. */\n readonly approvedAt: string;\n}\n\n/**\n * Run-level subprocessor register artifact (Issue #2174). Persisted as\n * `subprocessor-register.json` next to `compliance-annotations.json` and\n * `compliance-coverage-report.json`.\n *\n * The artifact is byte-stable: identical inputs produce identical\n * canonical JSON bytes, so a SHA-256 over the file is reproducible\n * across runs at the same commit. Subprocessors and cross-border\n * transfers are sorted by their stable identifier; the embedded\n * {@link SubprocessorRegister.merkleRoot} is a SHA-256 Merkle tree over\n * the canonical-JSON-serialised entries (subprocessors first, then\n * transfers, both pre-sorted), so a single root pin lets a downstream\n * verifier detect drift in either list without re-hashing the whole\n * file.\n */\nexport interface SubprocessorRegister {\n readonly schemaVersion: typeof SUBPROCESSOR_REGISTER_SCHEMA_VERSION;\n readonly registerVersion: typeof SUBPROCESSOR_REGISTER_VERSION;\n readonly generatedAt: string;\n readonly subprocessors: readonly SubprocessorEntry[];\n readonly crossBorderTransfers: readonly CrossBorderTransferEntry[];\n /** SHA-256 Merkle root over the (sorted) entry list, hex-encoded. */\n readonly merkleRoot: string;\n}\n\n/** Environment variable name that gates test-intelligence features at startup. */\nexport const TEST_INTELLIGENCE_ENV = \"TEST_INTELLIGENCE_ENABLED\" as const;\n\n/**\n * Environment variable name for the Wave 4 multi-source ingestion gate\n * (Issue #1431). Strictly nested behind {@link TEST_INTELLIGENCE_ENV}; the\n * resolver requires both gates _and_ the parent startup option to be enabled\n * before a job may compose more than one test-design source.\n */\nexport const TEST_INTELLIGENCE_MULTISOURCE_ENV =\n \"TEST_INTELLIGENCE_MULTISOURCE_ENABLED\" as const;\n\n/**\n * Schema version for the {@link MultiSourceTestIntentEnvelope} aggregate\n * (Issue #1431). Bumped on any breaking change to the envelope shape, the\n * source-ref shape, or the aggregate-hash construction.\n */\nexport const MULTI_SOURCE_TEST_INTENT_ENVELOPE_SCHEMA_VERSION =\n \"1.0.0\" as const;\n\n/** Schema version for persisted custom-context supporting source artifacts. */\nexport const CUSTOM_CONTEXT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for a persisted custom-context supporting source. */\nexport const CUSTOM_CONTEXT_ARTIFACT_FILENAME = \"custom-context.json\" as const;\n\n/** Stable source id for Markdown-authored custom context. */\nexport const CUSTOM_CONTEXT_MARKDOWN_SOURCE_ID =\n \"custom-context-markdown\" as const;\n\n/** Stable source id for structured-attribute custom context. */\nexport const CUSTOM_CONTEXT_STRUCTURED_SOURCE_ID =\n \"custom-context-structured\" as const;\n\n/** Version stamp for persisted role-separated LLM gateway evidence artifacts. */\nexport const LLM_GATEWAY_CONTRACT_VERSION = \"1.0.0\" as const;\n\n/** Schema version for the persisted `llm-capabilities.json` evidence artifact. */\nexport const LLM_CAPABILITIES_SCHEMA_VERSION = \"1.1.0\" as const;\n\n/** Canonical filename for the persisted LLM gateway capability probe artifact. */\nexport const LLM_CAPABILITIES_ARTIFACT_FILENAME =\n \"llm-capabilities.json\" as const;\n\n/**\n * Schema version for the persisted test-case validation report artifact (Issue #1364).\n * Bumped when `TestCaseValidationReport` changes shape.\n */\nexport const TEST_CASE_VALIDATION_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Schema version for the persisted policy decision report artifact (Issue #1364). */\nexport const TEST_CASE_POLICY_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Schema version for the persisted coverage / quality-signals report artifact (Issue #1364). */\nexport const TEST_CASE_COVERAGE_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Schema version for the persisted visual-sidecar validation report artifact (Issue #1364 / #1386). */\nexport const VISUAL_SIDECAR_VALIDATION_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted test-case payload accepted into review/export. */\nexport const GENERATED_TESTCASES_ARTIFACT_FILENAME =\n \"generated-testcases.json\" as const;\n\n/** Canonical filename for the persisted validation diagnostics artifact. */\nexport const TEST_CASE_VALIDATION_REPORT_ARTIFACT_FILENAME =\n \"validation-report.json\" as const;\n\n/** Canonical filename for the persisted policy-gate decision artifact. */\nexport const TEST_CASE_POLICY_REPORT_ARTIFACT_FILENAME =\n \"policy-report.json\" as const;\n\n/** Canonical filename for the persisted coverage / quality-signals artifact. */\nexport const TEST_CASE_COVERAGE_REPORT_ARTIFACT_FILENAME =\n \"coverage-report.json\" as const;\n\n/** Canonical filename for the persisted visual-sidecar validation artifact. */\nexport const VISUAL_SIDECAR_VALIDATION_REPORT_ARTIFACT_FILENAME =\n \"visual-sidecar-validation-report.json\" as const;\n\n/** Schema version for the persisted deterministic test-data oracle artifact (Issue #2071). */\nexport const TEST_DATA_ORACLE_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted deterministic test-data oracle artifact. */\nexport const TEST_DATA_ORACLE_REPORT_ARTIFACT_FILENAME =\n \"test-data-oracle-report.json\" as const;\n\n/**\n * Schema version for the persisted causal-validation report artifact\n * (Issue #2180). Bumped on any breaking change to the report shape, the\n * counterfactual-pair envelope, or the causal-coverage formula.\n */\nexport const CAUSAL_VALIDATION_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Canonical filename for the persisted causal-validation report artifact\n * (Issue #2180). The runner writes one `causal-validation-report.json`\n * per run when the causal-validation framework is enabled and embeds\n * the resulting summary into `policy-report.json#causalCoverage`.\n */\nexport const CAUSAL_VALIDATION_REPORT_ARTIFACT_FILENAME =\n \"causal-validation-report.json\" as const;\n\n/**\n * Default upper bound on the relative additional token cost the\n * causal-validation framework may incur per run (Issue #2180 FinOps\n * cap, \"no more than 30 % per run\"). Surfaced as a constant so CI can\n * assert the cap and the runner can refuse to dispatch causal-pair\n * generation that would exceed it. Pair generation is fully\n * deterministic and never calls an LLM; under default operation the\n * actual ratio is `0` — the cap is a hard ceiling, not a quota.\n */\nexport const CAUSAL_VALIDATION_TOKEN_BUDGET_RATIO_CAP = 0.3 as const;\n\n/**\n * Schema version for the persisted mutation-killing-eval report artifact\n * (Issue #2041). Bumped on any breaking change to the {@link MutationReport}\n * shape, the catalog identifier set, or the kill-rate aggregation formula.\n */\nexport const MUTATION_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Canonical filename for the persisted mutation-killing-eval report\n * artifact (Issue #2041). The runner writes one\n * `mutation-report.json` per run when mutation evaluation is enabled and\n * embeds the resulting summary into `policy-report.json#mutationKillRate`.\n */\nexport const MUTATION_REPORT_ARTIFACT_FILENAME =\n \"mutation-report.json\" as const;\n\n/**\n * Default kill-rate threshold (Issue #1753, #2041). Surfaced on the\n * `mutationKillRate` summary so downstream consumers can reproduce the\n * pass/fail decision deterministically without re-deriving the literal.\n * Tracks {@link DEFAULT_MUTATION_KILL_RATE_TARGET} verbatim so the\n * coverage-planner target and the runtime evaluator threshold cannot\n * drift; the alias is exposed under the evaluator-namespaced name so\n * call sites read self-explanatorily.\n */\nexport const MUTATION_KILL_RATE_DEFAULT_THRESHOLD = 0.85 as const;\n\n/**\n * Default share of the generator token budget reserved for mutation\n * evaluation (Issue #2041 FinOps cap). Surfaced as a constant so CI can\n * assert the cap and the runner can refuse to dispatch eval work that\n * would exceed it. The synthetic-SUT stub is fully deterministic and\n * never calls an LLM, so under default operation the actual ratio is\n * `0` — the cap is a hard ceiling, not a quota.\n */\nexport const MUTATION_EVAL_TOKEN_BUDGET_RATIO_CAP = 0.2 as const;\n\n/**\n * Closed set of mutation classes registered in the\n * mutation-killing-eval catalog (Issue #2041). The set is intentionally\n * small and append-only — every class documented here corresponds to a\n * known SUT-bug archetype the generated test suite is expected to\n * detect. Adding a new class is a MINOR contract bump; renaming or\n * retiring a class is MAJOR.\n *\n * - `field-required-flipped` — a previously-required input becomes\n * optional; cases asserting the required-input invariant kill it.\n * - `vat-applied-to-netto` — VAT incorrectly added to a netto amount;\n * cases asserting the VAT-exclusion invariant kill it.\n * - `currency-rounding-off-by-one` — final monetary totals drift by one\n * cent; cases asserting an exact two-decimal expected total kill it.\n * - `boundary-off-by-one` — a `>=` becomes `>` (or vice versa) at a\n * numeric / length boundary; cases asserting the exact boundary\n * kill it.\n * - `state-transition-skipped` — a workflow step is skipped (e.g. an\n * `auth` step is bypassed); workflow / navigation cases asserting\n * the transition kill it.\n * - `regex-relaxed` — a validation pattern accepts inputs it should\n * reject; validation cases asserting the rejected-input behavior\n * kill it.\n * - `null-equals-empty` — `null` is treated as an empty string\n * (or vice versa); cases asserting the null/empty distinction kill\n * it.\n * - `optional-cost-treated-required` — an optional cost field is\n * treated as required; cases that exercise the optional-flow kill\n * it.\n * - `currency-locale-confusion` — a euro amount is treated as a USD\n * amount in the calculation; cases that pin currency in the\n * expected total kill it.\n * - `error-message-suppressed` — a required error message no longer\n * surfaces; negative-flow cases asserting the error message kill it.\n * - `accessibility-name-removed` — a labelled element loses its\n * accessible name; a11y cases asserting the name kill it.\n * - `iban-checksum-skipped` — IBAN checksum validation is bypassed;\n * negative-flow cases that present an invalid IBAN kill it.\n * - `pii-redaction-disabled` — PII appears in a downstream artifact;\n * policy / compliance cases that assert redaction kill it.\n * - `four-eyes-principle-skipped` — a state-changing action skips the\n * four-eyes / dual-control gate; workflow cases asserting it kill\n * it.\n * - `audit-log-omitted` — a state-changing action no longer writes\n * the audit-log entry; cases asserting the audit row kill it.\n */\nexport const ALLOWED_MUTATION_CLASSES = [\n \"field-required-flipped\",\n \"vat-applied-to-netto\",\n \"currency-rounding-off-by-one\",\n \"boundary-off-by-one\",\n \"state-transition-skipped\",\n \"regex-relaxed\",\n \"null-equals-empty\",\n \"optional-cost-treated-required\",\n \"currency-locale-confusion\",\n \"error-message-suppressed\",\n \"accessibility-name-removed\",\n \"iban-checksum-skipped\",\n \"pii-redaction-disabled\",\n \"four-eyes-principle-skipped\",\n \"audit-log-omitted\",\n] as const;\nexport type MutationClass = (typeof ALLOWED_MUTATION_CLASSES)[number];\n\n/** Severity reported for a mutation that no test case kills. */\nexport const ALLOWED_MUTATION_SEVERITIES = [\"error\", \"warning\"] as const;\nexport type MutationSeverity = (typeof ALLOWED_MUTATION_SEVERITIES)[number];\n\n/** Per-mutation evaluation outcome (Issue #2041). */\nexport interface MutationEvaluation {\n /**\n * Stable mutation id matching `^MUT-[A-Z0-9-]{1,60}$` (the same\n * shape the catalog validator enforces). The default catalog uses\n * the `MUT-<CLASS>-<NN>` convention but the contract only\n * guarantees the regex shape so operator-registered mutations have\n * room to encode their own provenance.\n */\n readonly mutationId: string;\n /** Mutation class drawn from {@link ALLOWED_MUTATION_CLASSES}. */\n readonly mutationClass: MutationClass;\n /** Human-readable description of the synthetic bug being injected. */\n readonly description: string;\n /**\n * Stable provenance string, typically `\"Issue #2041 (registered)\"`.\n * Matches the convention used by the domain-invariant registry.\n */\n readonly source: string;\n /** Severity of an unkilled mutation (`\"error\"` for default catalog rows). */\n readonly severity: MutationSeverity;\n /**\n * Sorted, deduplicated test-case ids that are in scope for this\n * mutation (`applies === true`).\n */\n readonly applicableTestCaseIds: readonly string[];\n /**\n * Sorted, deduplicated test-case ids that detect this mutation\n * (`kills === true`).\n */\n readonly killingTestCaseIds: readonly string[];\n /**\n * `true` iff at least one accepted test case kills the mutation.\n * `false` covers two cases: (a) no case is in scope (the catalog\n * mutation does not apply to this run) and (b) every in-scope case\n * fails to detect the mutation.\n */\n readonly killed: boolean;\n /**\n * `true` iff at least one accepted test case is in scope for the\n * mutation. Drives the `applicableMutations` count surfaced in the\n * summary.\n */\n readonly applicable: boolean;\n}\n\n/** Per-class kill-rate row inside {@link MutationReport.byClass}. */\nexport interface MutationClassKillRate {\n /** Mutation class from {@link ALLOWED_MUTATION_CLASSES}. */\n readonly mutationClass: MutationClass;\n /** Total catalog mutations registered for this class. */\n readonly total: number;\n /** Mutations of this class with at least one in-scope test case. */\n readonly applicable: number;\n /** Mutations of this class killed by at least one test case. */\n readonly killed: number;\n /**\n * Kill rate for the class: `killed / applicable` rounded to six\n * digits. `0` when `applicable === 0` (no in-scope cases). Auditors\n * comparing per-class rates should consult `applicable` to decide\n * whether the rate is meaningful.\n */\n readonly killRate: number;\n}\n\n/**\n * Mutation-killing-eval report artifact (Issue #2041). Persisted as\n * `mutation-report.json` alongside `policy-report.json`. The byte-shape\n * is canonical: arrays are deterministically sorted, ratios are\n * rounded to six digits, and only set fields are written.\n */\nexport interface MutationReport {\n readonly schemaVersion: typeof MUTATION_REPORT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly generatedAt: string;\n readonly jobId: string;\n readonly policyProfileId: string;\n /** Total accepted test cases evaluated against the catalog. */\n readonly totalTestCases: number;\n /** Total mutations registered in the catalog. */\n readonly totalMutations: number;\n /** Mutations with at least one in-scope test case. */\n readonly applicableMutations: number;\n /** Mutations killed by at least one accepted test case. */\n readonly killedMutations: number;\n /**\n * Overall kill rate: `killedMutations / applicableMutations` rounded\n * to six digits. `0` when `applicableMutations === 0`.\n */\n readonly killRate: number;\n /** Configured kill-rate threshold (default: 0.85). */\n readonly threshold: number;\n /** Whether `killRate >= threshold`. */\n readonly meetsThreshold: boolean;\n /**\n * Per-class kill-rate rows ordered by the closed\n * {@link ALLOWED_MUTATION_CLASSES} insertion order so the byte-shape\n * stays stable when new classes are appended to the union. Every\n * catalog class appears exactly once.\n */\n readonly byClass: readonly MutationClassKillRate[];\n /**\n * Per-mutation evaluation rows ordered by `mutationId` ascending.\n * Every catalog mutation appears exactly once.\n */\n readonly mutations: readonly MutationEvaluation[];\n /**\n * Mutations that were applicable but not killed by any test case,\n * ordered by `mutationId` ascending. Surfaced as a convenience for\n * audit reports and CI summaries.\n */\n readonly unkilledMutations: readonly string[];\n}\n\n/* ====================================================================== *\n * Prompt-optimizer (Issue #2044, Wave 3 innovation roadmap). *\n * *\n * Replaces manual prompt curation with an offline, deterministic *\n * DSPy-style optimizer that mines bootstrapped few-shot exemplars from *\n * accepted runs, searches a constrained space of additive directive *\n * variants against a deterministic synthetic eval, and writes the best *\n * result as an *additive* lock-file entry. Standard runs are untouched: *\n * the optimizer only runs when the runner is invoked with *\n * `optimizePrompts: true` (CLI: `--optimize-prompts`). *\n * ====================================================================== */\n\n/** Optimizer module version. Bumped on breaking algorithmic changes. */\nexport const PROMPT_OPTIMIZER_VERSION = \"1.0.0\" as const;\n\n/** Schema version for the persisted optimizer report artifact. */\nexport const PROMPT_OPTIMIZER_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted optimizer report artifact. */\nexport const PROMPT_OPTIMIZER_REPORT_ARTIFACT_FILENAME =\n \"prompt-optimization-report.json\" as const;\n\n/**\n * Default quality gate (out of 100) above which an accepted-run case may\n * be promoted to a candidate few-shot exemplar. Tracks the issue spec.\n */\nexport const PROMPT_OPTIMIZER_DEFAULT_QUALITY_GATE = 90 as const;\n\n/**\n * Default FinOps multiplier capping the optimizer's total token budget\n * relative to a single benchmark run. Per Issue #2044 spec: 5x normal.\n */\nexport const PROMPT_OPTIMIZER_DEFAULT_BUDGET_MULTIPLIER = 5 as const;\n\n/**\n * Default search budget (number of candidate variants the random search\n * will evaluate per cycle). The cap stays small because the synthetic\n * eval is deterministic and inexpensive — operators bumping this should\n * also revisit {@link PROMPT_OPTIMIZER_DEFAULT_BUDGET_MULTIPLIER}.\n */\nexport const PROMPT_OPTIMIZER_DEFAULT_SEARCH_BUDGET = 16 as const;\n\n/** Default cap on how many bootstrapped exemplars a candidate may carry. */\nexport const PROMPT_OPTIMIZER_DEFAULT_MAX_FEW_SHOTS = 3 as const;\n\n/**\n * Closed set of additive optimizer directive ids the registry recognises.\n * Adding a directive id is a MINOR contract bump; renaming or retiring\n * one is MAJOR.\n *\n * - `prefer-figma-trace-screen-id` — every case must cite a screenId.\n * - `prefer-figma-trace-node-id` — every case must cite a nodeId.\n * - `cite-open-questions-verbatim` — open questions reproduced verbatim.\n * - `accessibility-name-required` — a11y cases name the labelled control.\n * - `boundary-coverage-explicit` — boundary cases pin the boundary value.\n * - `negative-flow-pin-error-text` — negative cases pin the error message.\n */\nexport const PROMPT_OPTIMIZER_DIRECTIVE_IDS = [\n \"prefer-figma-trace-screen-id\",\n \"prefer-figma-trace-node-id\",\n \"cite-open-questions-verbatim\",\n \"accessibility-name-required\",\n \"boundary-coverage-explicit\",\n \"negative-flow-pin-error-text\",\n] as const;\n\nexport type PromptOptimizerDirectiveId =\n (typeof PROMPT_OPTIMIZER_DIRECTIVE_IDS)[number];\n\n/**\n * One candidate prompt variant under evaluation by the optimizer. The\n * variant is fully described by its enabled directive ids and its\n * exemplar selection — the *base* prompt template is shared across all\n * candidates and remains the authoritative artifact (templates are\n * never replaced by a learned model; the optimizer only rewrites the\n * additive directive set on top of the pinned base).\n */\nexport interface PromptOptimizerCandidate {\n readonly candidateId: string;\n readonly directiveIds: readonly PromptOptimizerDirectiveId[];\n readonly fewShotExemplarIds: readonly string[];\n /**\n * Synthetic token cost charged for evaluating this candidate. The\n * optimizer refuses to dispatch any candidate whose cumulative cost\n * would exceed `budgetMultiplier * baselineTokenCost`.\n */\n readonly tokenCost: number;\n}\n\n/** Score earned by a {@link PromptOptimizerCandidate} on the eval set. */\nexport interface PromptOptimizerCandidateScore {\n readonly candidateId: string;\n /** Aggregate score (0-100) earned across the eval set. */\n readonly score: number;\n /** Per-directive points contributed (sums to {@link score}). */\n readonly directiveBreakdown: readonly {\n readonly directiveId: PromptOptimizerDirectiveId;\n readonly points: number;\n }[];\n /** Number of eval-set cases that received credit. */\n readonly passingCaseCount: number;\n /** Total eval-set case count. */\n readonly totalCaseCount: number;\n}\n\n/**\n * Bootstrapped few-shot exemplar mined from an accepted run. Exemplars\n * are content-addressed by `exemplarId` so optimizer outputs are\n * byte-stable regardless of source ordering.\n */\nexport interface PromptOptimizerExemplar {\n readonly exemplarId: string;\n readonly sourceRunId: string;\n readonly datasetId: string;\n /**\n * Quality score (0-100) carried over from the accepted run. Must be\n * `>= qualityGate` for the exemplar to be retained.\n */\n readonly score: number;\n /** Single accepted test case the exemplar represents. */\n readonly exemplarCaseId: string;\n /** SHA-256 of the exemplar payload (canonical-JSON over the case). */\n readonly contentSha256: string;\n}\n\n/**\n * Lock-file entry written *additively* to\n * the prompt-template version fixture under the `optimizedTemplates` array.\n * The base template's `promptCompilerSha256` pin is untouched so the\n * prompt-template-version CI guard cannot drift.\n */\nexport interface PromptOptimizationLockEntry {\n /** Stable identifier (`opt-<sha8>` of the report). */\n readonly optimizedTemplateId: string;\n /** Optimizer module version that produced the entry. */\n readonly optimizerVersion: typeof PROMPT_OPTIMIZER_VERSION;\n /** Base prompt template version this entry layers on top of. */\n readonly basePromptTemplateVersion: string;\n /** Dataset id the optimization cycle ran against. */\n readonly datasetId: string;\n /** Role-step id the candidate template targets. */\n readonly roleStepId: string;\n /** Random seed used by the search algorithm. */\n readonly seed: number;\n /** ISO-8601 timestamp captured at write time. */\n readonly generatedAt: string;\n /** Score the base template earned on the eval set (0-100). */\n readonly baselineScore: number;\n /** Score the optimized template earned on the eval set (0-100). */\n readonly optimizedScore: number;\n /** `optimizedScore - baselineScore`. */\n readonly improvementPoints: number;\n /** Directive ids enabled by the winning candidate. */\n readonly directiveIds: readonly PromptOptimizerDirectiveId[];\n /** Few-shot exemplar ids carried by the winning candidate. */\n readonly fewShotExemplarIds: readonly string[];\n /** SHA-256 of the canonical-JSON optimization report. */\n readonly reportSha256: string;\n}\n\n/**\n * Persisted optimizer report artifact. Byte-stable: arrays are sorted,\n * floats are rounded, only set fields are emitted.\n */\nexport interface PromptOptimizationReport {\n readonly schemaVersion: typeof PROMPT_OPTIMIZER_REPORT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly optimizerVersion: typeof PROMPT_OPTIMIZER_VERSION;\n readonly basePromptTemplateVersion: string;\n readonly generatedAt: string;\n readonly jobId: string;\n readonly datasetId: string;\n readonly roleStepId: string;\n readonly seed: number;\n readonly searchBudget: number;\n readonly qualityGate: number;\n readonly maxFewShots: number;\n readonly budgetMultiplier: number;\n /** Token budget envelope the cycle was given. */\n readonly tokenBudget: {\n readonly baselineTokenCost: number;\n readonly cap: number;\n readonly consumed: number;\n readonly withinCap: boolean;\n };\n /** Score of the unmodified base template on the eval set. */\n readonly baselineScore: number;\n /** Score of the winning candidate. */\n readonly optimizedScore: number;\n /** Additive lift the cycle delivered. */\n readonly improvementPoints: number;\n /** All exemplars considered after the quality gate. */\n readonly exemplars: readonly PromptOptimizerExemplar[];\n /** All candidates evaluated, ordered by descending score then id. */\n readonly candidates: readonly PromptOptimizerCandidate[];\n /** Per-candidate scores, ordered by descending score then id. */\n readonly candidateScores: readonly PromptOptimizerCandidateScore[];\n /** Optimized lock-file entry. */\n readonly lockEntry: PromptOptimizationLockEntry;\n /**\n * PROV-DM provenance node (B.10) for this optimization activity, so\n * downstream graph builders can attach it without re-deriving the\n * shape. The node is informed by the base template version and\n * generated by the optimizer module.\n */\n readonly provenance: {\n readonly activityId: string;\n readonly entityId: string;\n readonly wasInformedBy: string;\n readonly wasGeneratedAt: string;\n };\n}\n\n/** Schema version for the deterministic pipeline quality passport artifact. */\nexport const PIPELINE_QUALITY_PASSPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted pipeline quality passport artifact. */\nexport const PIPELINE_QUALITY_PASSPORT_ARTIFACT_FILENAME =\n \"quality-passport.json\" as const;\n\n/**\n * Schema version for the persisted self-verify rubric pass artifact (Issue #1379).\n *\n * Bumped on any breaking change to the per-case evaluation shape, the\n * job-level aggregate shape, the rubric-dimension union, or the JSON\n * response shape consumed by the rubric prompt.\n */\nexport const SELF_VERIFY_RUBRIC_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Canonical filename for the persisted self-verify rubric report\n * (Issue #1379). The artifact is emitted under\n * `<runDir>/testcases/self-verify-rubric.json` when the opt-in pass runs.\n */\nexport const SELF_VERIFY_RUBRIC_REPORT_ARTIFACT_FILENAME =\n \"self-verify-rubric.json\" as const;\n\n/**\n * Run-dir-relative subdirectory under which the self-verify rubric artifact\n * is persisted. Sibling to the validation reports so consumers can locate\n * the test-case quality signals next to the cases they describe.\n */\nexport const SELF_VERIFY_RUBRIC_ARTIFACT_DIRECTORY = \"testcases\" as const;\n\n/**\n * Prompt template version stamp for the rubric-only prompt family. Bumped\n * on any change to the system prompt, user-prompt preamble, or the JSON\n * response schema; the version stamp participates in the rubric replay-cache\n * key so any template change forces a cache miss.\n */\nexport const SELF_VERIFY_RUBRIC_PROMPT_TEMPLATE_VERSION = \"1.0.0\" as const;\n\n/** Stable JSON schema name attached to the structured rubric response. */\nexport const SELF_VERIFY_RUBRIC_RESPONSE_SCHEMA_NAME =\n \"SelfVerifyRubricReport\" as const;\n\n/** Schema version for the persisted review-gate state and event-log artifacts (Issue #1365). */\nexport const REVIEW_GATE_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Schema version for the persisted QC mapping preview artifact (Issue #1365). */\nexport const QC_MAPPING_PREVIEW_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Schema version for the persisted export-report artifact (Issue #1365). */\nexport const EXPORT_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Schema version stamp embedded in the OpenText ALM reference XML export (Issue #1365). */\nexport const ALM_EXPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted review-gate event log. */\nexport const REVIEW_EVENTS_ARTIFACT_FILENAME = \"review-events.json\" as const;\n\n/** Canonical filename for the persisted review-gate snapshot. */\nexport const REVIEW_STATE_ARTIFACT_FILENAME = \"review-state.json\" as const;\n\n/** Canonical filename for the persisted JSON export of approved test cases. */\nexport const EXPORT_TESTCASES_JSON_ARTIFACT_FILENAME =\n \"testcases.json\" as const;\n\n/** Canonical filename for the persisted CSV export of approved test cases. */\nexport const EXPORT_TESTCASES_CSV_ARTIFACT_FILENAME = \"testcases.csv\" as const;\n\n/** Canonical filename for the optional persisted XLSX export of approved test cases. */\nexport const EXPORT_TESTCASES_XLSX_ARTIFACT_FILENAME =\n \"testcases.xlsx\" as const;\n\n/** Canonical filename for the persisted OpenText ALM reference XML export. */\nexport const EXPORT_TESTCASES_ALM_XML_ARTIFACT_FILENAME =\n \"testcases.alm.xml\" as const;\n\n/** Canonical filename for the persisted QC mapping preview artifact. */\nexport const QC_MAPPING_PREVIEW_ARTIFACT_FILENAME =\n \"qc-mapping-preview.json\" as const;\n\n/** Canonical filename for the persisted export-report artifact. */\nexport const EXPORT_REPORT_ARTIFACT_FILENAME = \"export-report.json\" as const;\n\n/** Built-in OpenText ALM reference export profile id (Wave 1). */\nexport const OPENTEXT_ALM_REFERENCE_PROFILE_ID =\n \"opentext-alm-default\" as const;\n\n/** Version stamp for the built-in OpenText ALM reference export profile. */\nexport const OPENTEXT_ALM_REFERENCE_PROFILE_VERSION = \"1.0.0\" as const;\n\n/** XML namespace embedded in the OpenText ALM reference export root element. */\nexport const ALM_EXPORT_XML_NAMESPACE =\n \"https://workspace-dev.local/schema/alm-export/v1\" as const;\n\n/**\n * Built-in policy profile id for the default EU-banking compliance gate.\n * Operators may install additional profiles by version stamp; this id is the\n * one Wave 1 ships with.\n */\nexport const EU_BANKING_DEFAULT_POLICY_PROFILE_ID =\n \"eu-banking-default\" as const;\n\n/** Version stamp for the built-in `eu-banking-default` policy profile. */\nexport const EU_BANKING_DEFAULT_POLICY_PROFILE_VERSION = \"1.0.0\" as const;\n\n/**\n * Issue #2187 — Built-in policy-profile identifier for the\n * sovereign-cloud / air-gap deployment topology used by DE Sparkassen,\n * Volksbanken, and on-prem-only insurers. Routes all model calls through\n * the operator-configured sovereign-cloud LLM gateway and refuses any\n * deployment outside the customer-approved hosting-region allow-list.\n */\nexport const EU_BANKING_SOVEREIGN_POLICY_PROFILE_ID =\n \"eu-banking-sovereign\" as const;\n\n/** Version stamp for the built-in `eu-banking-sovereign` policy profile. */\nexport const EU_BANKING_SOVEREIGN_POLICY_PROFILE_VERSION = \"1.0.0\" as const;\n\n/**\n * Allowed test-case validation issue codes (Issue #1364).\n * The list is the runtime source of truth; new codes plug in here without\n * altering call sites. Adding a new code is a minor (additive) bump.\n */\nexport const ALLOWED_TEST_CASE_VALIDATION_ISSUE_CODES = [\n \"schema_invalid\",\n \"missing_trace\",\n \"trace_screen_unknown\",\n \"missing_expected_results\",\n \"steps_unordered\",\n \"steps_indices_non_sequential\",\n \"step_action_empty\",\n \"step_action_too_long\",\n \"duplicate_step_index\",\n \"duplicate_test_case_id\",\n \"title_empty\",\n \"objective_empty\",\n \"risk_category_invalid_for_intent\",\n \"qc_mapping_blocking_reasons_missing\",\n \"qc_mapping_exportable_inconsistent\",\n \"quality_signals_confidence_out_of_range\",\n \"quality_signals_coverage_unknown_id\",\n \"test_data_pii_detected\",\n \"test_data_unredacted_value\",\n \"preconditions_pii_detected\",\n \"expected_results_pii_detected\",\n \"assumptions_excessive\",\n \"open_questions_excessive\",\n \"ambiguity_without_review_state\",\n \"unsupported_unresolved_validation_detail\",\n \"needs_open_question_clarification\",\n \"semantic_suspicious_content\",\n \"domain_invariant_violation\",\n \"test_data_oracle_violation\",\n \"truncated_repair_instruction\",\n \"missing_field_lifecycle_transition\",\n \"unknown_field_lifecycle_transition\",\n \"uncovered_field_lifecycle_transition\",\n /**\n * Issue #2168 — non-blocking counterpart to\n * `uncovered_field_lifecycle_transition`. Fired when a field-lifecycle\n * transition classified as `recommended_positive_path` (positive-path\n * completion) or `state_transition_test_only` (reset/edit flows, only\n * material when the run carries a `technique === \"state_transition\"` case)\n * has no anchored test step. Always `warning` so the run is not blocked\n * by missing positive-path coverage that previously over-fired\n * 30–139× per dataset on M0 benchmarks.\n */\n \"uncovered_field_lifecycle_transition_recommended\",\n /**\n * Issue #2123 — two cases share the same\n * {@link EquivalenceClassFingerprint} (covered fields, covered actions,\n * risk class, technique, oracle polarity) AND fail to add real coverage\n * relative to the class representative (no new oracle category, no\n * different action subset, no different state path). Always `warning`.\n */\n \"intra_equivalence_class_redundancy\",\n /**\n * Issue #2123 — two cases produce near-identical TEXT (title + ordered\n * step actions) within a Levenshtein-2 character-distance budget.\n * Retained as a SEPARATE auditor signal alongside the equivalence-class\n * check so a reviewer can spot wording duplicates that DO add real\n * coverage. Always `warning`.\n */\n \"exact_near_duplicate_text\",\n] as const;\n\nexport type TestCaseValidationIssueCode =\n (typeof ALLOWED_TEST_CASE_VALIDATION_ISSUE_CODES)[number];\n\n/** Severity surfaced for a single validation issue. */\nexport type TestCaseValidationSeverity = \"error\" | \"warning\" | \"info\";\n\n/** Single semantic / structural validation issue. */\nexport interface TestCaseValidationIssue {\n testCaseId?: string;\n path: string;\n code: TestCaseValidationIssueCode;\n severity: TestCaseValidationSeverity;\n message: string;\n}\n\n/** Aggregate validation outcome across one job's generated test cases. */\nexport interface TestCaseValidationReport {\n schemaVersion: typeof TEST_CASE_VALIDATION_REPORT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n generatedAt: string;\n jobId: string;\n totalTestCases: number;\n errorCount: number;\n warningCount: number;\n /** Whether the report blocks downstream review/export (any error => true). */\n blocked: boolean;\n /**\n * Availability state of the optional cross-family judges as observed by the\n * validation pipeline. `skipped` means no verdict was supplied; `refused`\n * means a verdict arrived with a refusal payload.\n */\n judgeAvailability?: JudgeAvailabilityReport;\n issues: TestCaseValidationIssue[];\n}\n\nexport const ALLOWED_JUDGE_AVAILABILITY_STATES = [\n \"available\",\n \"refused\",\n \"skipped\",\n] as const;\n\nexport type JudgeAvailabilityState =\n (typeof ALLOWED_JUDGE_AVAILABILITY_STATES)[number];\n\nexport interface JudgeAvailabilityReport {\n readonly faithfulness: JudgeAvailabilityState;\n readonly a11y: JudgeAvailabilityState;\n}\n\n/**\n * Allowed policy-gate decisions (Issue #1364).\n *\n * - `approved` — case may proceed to review/export as-is.\n * - `blocked` — case must not reach review or export.\n * - `needs_review` — case must be reviewed manually before export.\n */\nexport const ALLOWED_TEST_CASE_POLICY_DECISIONS = [\n \"approved\",\n \"blocked\",\n \"needs_review\",\n] as const;\nexport type TestCasePolicyDecision =\n (typeof ALLOWED_TEST_CASE_POLICY_DECISIONS)[number];\n\n/**\n * Allowed policy outcome codes attached to a single decision row.\n * Visual-sidecar codes (`visual_*`) come from the multimodal sidecar\n * gating per the Issue #1364 / #1386 update.\n */\nexport const ALLOWED_TEST_CASE_POLICY_OUTCOMES = [\n \"missing_trace\",\n \"missing_expected_results\",\n \"pii_in_test_data\",\n \"acceptance_criterion_uncovered\",\n \"ict_register_ref_required\",\n \"technique_quota_breach\",\n \"p0_risk_element_uncovered\",\n \"missing_negative_or_validation_for_required_field\",\n \"missing_accessibility_case\",\n \"missing_boundary_case\",\n \"schema_invalid\",\n \"duplicate_test_case\",\n \"regulated_risk_review_required\",\n \"ambiguity_review_required\",\n \"qc_mapping_not_exportable\",\n \"low_confidence_review_required\",\n \"open_questions_review_required\",\n \"visual_sidecar_failure\",\n \"visual_sidecar_fallback_used\",\n \"visual_sidecar_fallback_used_succeeded\",\n \"visual_sidecar_both_failed\",\n \"visual_sidecar_low_confidence\",\n \"visual_sidecar_possible_pii\",\n \"visual_sidecar_prompt_injection_text\",\n \"semantic_suspicious_content\",\n \"cross_modal_faithfulness_evaluation_missing\",\n \"cross_modal_faithfulness_score_below_threshold\",\n \"cross_modal_faithfulness_case_level_fallback\",\n \"cross_modal_faithfulness_partial_majority\",\n \"judge_refused\",\n \"risk_tag_downgrade_detected\",\n \"custom_context_risk_escalation\",\n \"multi_source_conflict_present\",\n \"coverage_drift_exceeded\",\n \"a11y_criterion_covered_weakly\",\n \"a11y_criterion_not_covered\",\n] as const;\nexport type TestCasePolicyOutcome =\n (typeof ALLOWED_TEST_CASE_POLICY_OUTCOMES)[number];\n\n/** Single policy-rule violation surfaced for a generated test case. */\nexport interface TestCasePolicyViolation {\n rule: string;\n outcome: TestCasePolicyOutcome;\n severity: TestCaseValidationSeverity;\n reason: string;\n /** JSON-pointer-style path inside the test case if applicable. */\n path?: string;\n}\n\n/** Per-test-case policy decision row. */\nexport interface TestCasePolicyDecisionRecord {\n testCaseId: string;\n decision: TestCasePolicyDecision;\n violations: TestCasePolicyViolation[];\n}\n\n/** Aggregate policy report across one job's generated test cases. */\nexport interface TestCasePolicyReport {\n schemaVersion: typeof TEST_CASE_POLICY_REPORT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n generatedAt: string;\n jobId: string;\n policyProfileId: string;\n policyProfileVersion: string;\n totalTestCases: number;\n approvedCount: number;\n blockedCount: number;\n needsReviewCount: number;\n /** Mean calibrated per-case confidence across every generated case. */\n confidenceMean?: number;\n /** 10th percentile calibrated per-case confidence across every case. */\n confidenceP10?: number;\n /** 50th percentile calibrated per-case confidence across every case. */\n confidenceP50?: number;\n /** 90th percentile calibrated per-case confidence across every case. */\n confidenceP90?: number;\n /** Whether ANY case was blocked (downstream export gate). */\n blocked: boolean;\n decisions: TestCasePolicyDecisionRecord[];\n /** Job-level policy violations (e.g., job-wide duplicate fingerprint). */\n jobLevelViolations: TestCasePolicyViolation[];\n /**\n * Optional provenance seal summary for runs that persisted\n * `provenance.jsonld` (Issue #2037). Carries only the Merkle root and\n * artifact identity so auditors can correlate `policy-report.json` with the\n * provenance bundle without embedding the full graph here.\n */\n provenance?: TestCasePolicyProvenanceSummary;\n /**\n * Optional structured quality-gate evaluations carried alongside the\n * decision records (Issue #2053). Today this surfaces the\n * `G-NEG-CASE` adversarial-critic negative-case-lift hard gate; future\n * gates extend the same array. The field is omitted when no quality\n * gates were evaluated for the run, so the byte-shape stays stable for\n * legacy runs that pre-date the gate registry.\n */\n gateResults?: TestCasePolicyGateResult[];\n /**\n * Optional mutation-killing-eval summary (Issue #2041). Surfaces the\n * top-level `mutationKillRate` KPI alongside per-class kill rates so\n * downstream auditors can answer \"how effective is this generated test\n * suite at detecting injected SUT bugs?\" without re-running the\n * evaluator. The block is omitted when the mutation evaluator was not\n * run for this job (default for fast iterative runs), so the byte\n * shape stays stable for runs that pre-date the evaluator.\n */\n mutationKillRate?: MutationKillRateSummary;\n /**\n * Optional causal-coverage KPI summary (Issue #2180). Surfaces the\n * top-level `causalCoverage` ratio alongside hypothesis evaluation\n * counts so downstream auditors can answer \"what share of declared\n * causal hypotheses did the suite satisfy?\" without re-running the\n * framework. The block is omitted when the causal-validation\n * framework was not run for this job (default for fast iterative\n * runs), so the byte shape stays stable for runs that pre-date the\n * framework.\n */\n causalCoverage?: CausalCoverageSummary;\n /**\n * Issue #2116 — explicit semantics + audit trail for the\n * cross-modal-faithfulness gate's tier-elastic fallback.\n *\n * Always emitted by the policy gate so reviewers can tell at a glance\n * whether the gate reasoned over per-step evidence\n * (`mode === \"per_step\"`), fell back to the verdict's case-level score\n * (`mode === \"case_level_fallback\"`), or had no faithfulness verdict\n * at all (`mode === \"missing\"`). The `requirePerStepFaithfulness`\n * field mirrors the active profile rule so audit reviewers do not\n * need a second artifact to know whether the fallback was demoted to\n * an error.\n */\n faithfulnessEvaluation?: FaithfulnessEvaluationSummary;\n}\n\n/** Issue #2116 — audit-trail block summarizing how the\n * cross-modal-faithfulness gate evaluated a job. Carried on\n * `TestCasePolicyReport.faithfulnessEvaluation`. */\nexport interface FaithfulnessEvaluationSummary {\n /** Evaluation mode the gate applied. */\n readonly mode: FaithfulnessEvaluationMode;\n /**\n * Whether the active profile escalated the\n * `case_level_fallback` mode to a blocking error (Issue #2116\n * `requirePerStepFaithfulness`). Mirrors the rule for downstream\n * consumers without forcing them to re-resolve the profile.\n */\n readonly requirePerStepFaithfulness: boolean;\n /**\n * Reviewer-readable explanation of why the gate ended up in\n * {@link mode}. Stable across runs with identical inputs so the\n * persisted policy report is byte-deterministic.\n */\n readonly reason: string;\n /**\n * Step-verdict count consumed by the gate when\n * `mode === \"per_step\"`. `0` for `case_level_fallback` and\n * `missing` so the field is always present and machine-comparable.\n */\n readonly stepVerdictCount: number;\n}\n\n/**\n * Compact mutation-killing-eval summary embedded into\n * `policy-report.json#mutationKillRate` (Issue #2041). Each field is\n * derived from the persisted `mutation-report.json` artifact and can be\n * used as a hard-gate input by downstream CI without parsing the full\n * report.\n */\nexport interface MutationKillRateSummary {\n /** Stable artifact filename auditors should consult for the full report. */\n readonly artifactFilename: typeof MUTATION_REPORT_ARTIFACT_FILENAME;\n /**\n * Overall fraction of registered mutations killed by at least one\n * accepted test case, in [0, 1]. Rounded to six digits to match the\n * canonical-JSON contract. `0` when the catalog is empty or no\n * mutations were in scope for the run.\n */\n readonly killRate: number;\n /** Total number of registered mutations in the catalog. */\n readonly totalMutations: number;\n /** Mutations that had at least one in-scope test case. */\n readonly applicableMutations: number;\n /** Mutations killed by at least one accepted test case. */\n readonly killedMutations: number;\n /**\n * Configured KPI threshold (Issue #1753 set `>= 0.85`). Surfaced so\n * downstream consumers can reproduce the pass/fail computation\n * without consulting the runner config.\n */\n readonly threshold: number;\n /**\n * Whether the kill rate meets or exceeds the threshold. Recorded\n * deterministically at write time so the gate decision survives\n * re-reads of the report.\n */\n readonly meetsThreshold: boolean;\n}\n\n/**\n * Compact causal-coverage summary embedded into\n * `policy-report.json#causalCoverage` (Issue #2180). Mirrors the\n * top-level KPI fields from `causal-validation-report.json` so\n * downstream consumers can read the headline ratio without parsing the\n * full report. All fields are deterministic and round-trip stable.\n */\nexport interface CausalCoverageSummary {\n /** Stable artifact filename auditors should consult for the full report. */\n readonly artifactFilename: typeof CAUSAL_VALIDATION_REPORT_ARTIFACT_FILENAME;\n /** Number of distinct causal hypotheses evaluated for the run. */\n readonly hypothesesEvaluated: number;\n /** Total counterfactual pairs generated across every hypothesis. */\n readonly pairsGenerated: number;\n /**\n * Pairs whose embedded causal assertion was violated by the\n * generated suite. A non-zero count signals a SUT-side bug surfaced\n * by the counterfactual layer (the harness produced a pair that\n * contradicts the declared hypothesis), not a harness fault.\n */\n readonly pairsViolated: number;\n /**\n * Causal-coverage ratio in `[0, 1]`, rounded to six digits to match\n * the canonical-JSON contract. Defined as\n * `(pairsGenerated - pairsViolated) / pairsGenerated`, i.e. the\n * share of evaluated counterfactual pairs that satisfy their\n * declared hypothesis. `0` when no pairs were generated.\n */\n readonly causalCoverageRatio: number;\n}\n\n/**\n * Closed set of quality-gate identifiers the production runner may\n * evaluate. The set is intentionally small and append-only — every gate\n * id documented here corresponds to an enforceable hard gate with\n * persisted, auditable evaluation evidence.\n *\n * - `G-NEG-CASE` (Issue #2053): adversarial-critic negative-case-lift\n * hard gate. Compares the per-run baseline negative-case ratio\n * against the post-critic final negative-case ratio and asserts the\n * relative ratio increase meets the configured threshold (default\n * `0.30`).\n */\nexport const ALLOWED_TEST_CASE_POLICY_GATE_IDS = [\"G-NEG-CASE\"] as const;\nexport type TestCasePolicyGateId =\n (typeof ALLOWED_TEST_CASE_POLICY_GATE_IDS)[number];\n\n/**\n * Closed set of statuses a quality-gate evaluation may report. The\n * status surfaced for a below-threshold observation depends on the\n * gate's configured `gateMode`: `enforce` produces `failed` and the\n * runner exits with a non-zero failure class; `advisory` produces\n * `advisory` and the run completes successfully. `failed` and\n * `advisory` are therefore mutually exclusive — they never both appear\n * for the same evaluation.\n *\n * - `passed`: the gate's threshold was met.\n * - `failed`: the gate's threshold was not met and `gateMode ===\n * \"enforce\"`. The production runner exits with a non-zero failure\n * class so CI marks the build red.\n * - `advisory`: the gate's threshold was not met but `gateMode ===\n * \"advisory\"`. The result is recorded for audit and the run\n * completes successfully.\n * - `skipped`: the gate could not be evaluated (the upstream signal it\n * depends on did not run, or the gate was explicitly disabled). Skip\n * is an explicit, audit-visible status — it is never silently a\n * pass.\n */\nexport const ALLOWED_TEST_CASE_POLICY_GATE_STATUSES = [\n \"passed\",\n \"failed\",\n \"advisory\",\n \"skipped\",\n] as const;\nexport type TestCasePolicyGateStatus =\n (typeof ALLOWED_TEST_CASE_POLICY_GATE_STATUSES)[number];\n\n/**\n * Closed set of skip reasons recorded when a quality gate's status is\n * `\"skipped\"`. Each reason documents *why* the gate could not be\n * evaluated so the audit trail can distinguish a missing input from a\n * deliberate disable.\n *\n * - `adversarial_critic_disabled`: the adversarial-critic loop did not\n * run (no `logicJudge` client wired or the loop was suppressed by an\n * upstream condition); the gate has no trace artifact to evaluate.\n * - `adversarial_critic_failed`: the adversarial-critic loop ran but\n * exited with `stopReason === \"critic_failed\"`; the trace artifact\n * exists but the negative-coverage accounting cannot be trusted.\n * - `gate_disabled`: the operator explicitly set\n * `negativeCaseLift.gateMode === \"off\"` for this run.\n */\nexport const ALLOWED_TEST_CASE_POLICY_GATE_SKIP_REASONS = [\n \"adversarial_critic_disabled\",\n \"adversarial_critic_failed\",\n \"gate_disabled\",\n] as const;\nexport type TestCasePolicyGateSkipReason =\n (typeof ALLOWED_TEST_CASE_POLICY_GATE_SKIP_REASONS)[number];\n\n/**\n * Per-gate evaluation record persisted in `policy-report.json` under\n * `gateResults`. The shape is intentionally compact: callers correlate\n * the record back to its source artifact via `ruleRef` (a stable\n * pointer that names the artifact filename or the contract symbol the\n * gate consulted) and use `observedRatio` / `thresholdRatio` for\n * audit-grade comparison.\n *\n * Issue #2053 ships the `G-NEG-CASE` instance grounded in the\n * `AdversarialCriticTraceArtifact.negativeCoverage` block. Future gates\n * follow the same structure.\n */\nexport interface TestCasePolicyGateResult {\n /** Stable gate identifier; see {@link ALLOWED_TEST_CASE_POLICY_GATE_IDS}. */\n readonly gateId: TestCasePolicyGateId;\n /** Evaluation outcome; see {@link ALLOWED_TEST_CASE_POLICY_GATE_STATUSES}. */\n readonly status: TestCasePolicyGateStatus;\n /**\n * Stable rule reference — typically a `ti:`-prefixed contract symbol\n * or an artifact filename — so auditors can resolve the gate's\n * source evidence without the runner having to embed it.\n */\n readonly ruleRef: string;\n /**\n * Threshold the gate compared against, when the gate is\n * threshold-shaped (e.g. ratio ≥ 0.30). Omitted for skip-only\n * outcomes that did not consult a threshold.\n */\n readonly thresholdRatio?: number;\n /**\n * Observed value the gate produced (e.g. relative ratio increase).\n * Omitted when the gate was skipped before the value was computed.\n */\n readonly observedRatio?: number;\n /**\n * Skip reason when `status === \"skipped\"`; absent otherwise. See\n * {@link ALLOWED_TEST_CASE_POLICY_GATE_SKIP_REASONS} for the closed\n * set of values.\n */\n readonly skipReason?: TestCasePolicyGateSkipReason;\n /**\n * Human-readable explanation suitable for surfacing in CI logs and\n * audit reports. Always present so consumers do not have to derive a\n * message from the structured fields.\n */\n readonly message: string;\n}\n\n/** Additive provenance summary duplicated into `policy-report.json`. */\nexport interface TestCasePolicyProvenanceSummary {\n readonly artifactFilename: string;\n readonly merkleAlgorithm: \"sha256_merkle_v1\";\n readonly merkleRoot: string;\n readonly leafCount: number;\n}\n\n/**\n * Runtime summary of an active model binding used by a job. The optional\n * `ictRegisterRef` is enforced by the banking policy gate when provided to\n * policy evaluation and is attested in evidence artifacts when persisted.\n */\nexport interface ActiveModelBinding {\n /** Stable provider / operator identifier for the bound model. */\n readonly providerId: string;\n /** Stable model identifier inside the provider namespace. */\n readonly modelId: string;\n /**\n * Optional deployment / inference-profile identifier that distinguishes\n * multiple ICT services serving the same base model.\n */\n readonly inferenceProfileId?: string;\n /**\n * Optional operator-managed ICT register reference. Mandatory for banking\n * policy enforcement when the binding is active for a regulated job.\n */\n readonly ictRegisterRef?: string;\n /**\n * Optional coarse deployment-region marker surfaced by routing and used by\n * region-attestation-aware policy gates to cross-check runtime evidence.\n */\n readonly region?: JudgeModelRegion;\n}\n\n/** Tunable knobs of a policy profile (defaults shown for `eu-banking-default`). */\nexport interface FinOpsWallClockBudgetPolicy {\n readonly baseMs: number;\n readonly perCaseMs: number;\n readonly perAdditionalJudgeMs: number;\n readonly perAdversarialRoundMs: number;\n readonly visualSidecarMs: number;\n readonly hardCeilingMs: number;\n}\n\n/** Tunable knobs of a policy profile (defaults shown for `eu-banking-default`). */\nexport interface TestCasePolicyProfileRules {\n /** Risk categories that always require manual review. */\n reviewOnlyRiskCategories: TestCaseRiskCategory[];\n /** Risk categories that block export when missing trace/expected/PII checks fail. */\n strictRiskCategories: TestCaseRiskCategory[];\n /** Whether a screen with form fields requires at least one accessibility case. */\n requireAccessibilityCaseWhenFormPresent: boolean;\n /** Whether each detected validation rule requires at least one negative/validation case. */\n requireNegativeOrValidationForValidationRules: boolean;\n /** Whether each required field requires at least one boundary case. */\n requireBoundaryCaseForRequiredFields: boolean;\n /** Min generator-side confidence; below this threshold => needs_review. */\n minConfidence: number;\n /** Max Jaccard similarity above which two cases are flagged as duplicates. */\n duplicateSimilarityThreshold: number;\n /** Max open-question count per case before review is required. */\n maxOpenQuestionsPerCase: number;\n /** Max assumption count per case before review is required. */\n maxAssumptionsPerCase: number;\n /**\n * Operator-configurable handling when a cross-family judge refuses.\n * Omitted values preserve the legacy availability-first posture\n * (`fail_open`) for backwards compatibility.\n */\n judgeRefusalPolicy?: JudgeRefusalPolicyConfig;\n /**\n * Whether the policy gate must cross-reference each generated test case's\n * declared `riskCategory` against the risk classification derivable from the\n * Business Test Intent IR for the screens referenced in the case's\n * `figmaTraceRefs`. When enabled (the secure default), any case that\n * declares a risk category outside `reviewOnlyRiskCategories` while the\n * intent IR derives a review-only classification for one of its screens\n * raises a `risk_tag_downgrade_detected` outcome at both per-case and\n * job-level. The case is escalated to `needs_review` (defense-in-depth\n * against an out-of-band caller submitting forged low-risk tags).\n *\n * Optional for backward compatibility. Treat `undefined` as `true`.\n */\n enforceRiskTagDowngradeDetection?: boolean;\n /**\n * Minimum job-level field-coverage ratio required by the logic-judge\n * coverage hard-gate (Issue #1901). Computed as\n * `actionCoverage.covered / actionCoverage.total` across the generated\n * test case list. Below this threshold the judge emits the\n * `insufficient_coverage_breadth` finding (severity: error) and the\n * repair-loop is triggered. Tunable per profile; the secure default for\n * `eu-banking-default` is `0.4`. Optional for backward compatibility:\n * when omitted the hard-gate skips the breadth check.\n */\n fieldCoverageRatioMin?: number;\n /**\n * Minimum job-level action-coverage ratio required by the logic-judge\n * coverage hard-gate (Issue #1901). Computed as\n * `actionCoverage.covered / actionCoverage.total` across the generated\n * test case list. Below this threshold the judge emits the\n * `insufficient_coverage_breadth` finding (severity: error) and the\n * repair-loop is triggered. Tunable per profile; the secure default for\n * `eu-banking-default` is `0.5`. Optional for backward compatibility:\n * when omitted the hard-gate skips the breadth check.\n */\n actionCoverageRatioMin?: number;\n /**\n * Issue #2053 — adversarial-critic negative-case-lift hard gate\n * (`G-NEG-CASE`). When the adversarial-critic loop runs for a job,\n * the production runner compares the per-run baseline negative-case\n * ratio against the post-critic final negative-case ratio. The\n * relative ratio increase must meet `thresholdRatio` (default\n * `0.30`).\n *\n * `gateMode` controls enforcement:\n *\n * - `\"enforce\"` (the secure default for `eu-banking-default`): a\n * below-threshold result fails the run with the\n * `NEGATIVE_CASE_LIFT_BELOW_THRESHOLD` failure class so CI marks\n * the build red.\n * - `\"advisory\"`: the gate result is persisted in\n * `policy-report.json` but does not fail the run. Intended for\n * fast iterative local runs that should still record the metric.\n * - `\"off\"`: the gate is not evaluated; the entry persisted in the\n * policy report records `status === \"skipped\"` with\n * `skipReason === \"gate_disabled\"` so audit can distinguish a\n * deliberate disable from a missing upstream signal.\n *\n * When the adversarial-critic loop did not run (no judge client\n * wired) or exited with `stopReason === \"critic_failed\"`, the gate\n * status is always `\"skipped\"` regardless of `gateMode` — skip is\n * an explicit, audit-visible status and is never silently a pass. When\n * the runner applies the deterministic negative-case fallback, the trace\n * records `stopReason === \"deterministic_fallback_applied\"` and the\n * gate is evaluated normally against the resulting coverage accounting.\n *\n * Optional for backward compatibility: when omitted the production\n * runner falls back to the documented default of\n * `{ gateMode: \"enforce\", thresholdRatio: 0.30 }`.\n */\n negativeCaseLift?: {\n readonly gateMode: \"enforce\" | \"advisory\" | \"off\";\n readonly thresholdRatio: number;\n };\n /**\n * Issue #2068 — `policy:technique-coverage-minimum` resolution mode.\n *\n * Drives whether the gate enforces the planner's fixed\n * `CoveragePlan.perScreen[].techniqueQuotas` rows verbatim\n * (`{ mode: \"fixed\" }`) or replaces the equivalence-partitioning\n * quota with a tier-elastic formula that scales with the screen's\n * coverage-relevant field count (`{ mode: \"tier-elastic\" }`).\n *\n * Optional for backwards compatibility: when omitted the gate falls\n * back to `tier-elastic` so small-screen masks with `<= 8` fields\n * stop tripping the closeout-blocking 12-EP floor that was the\n * original closeout child #2026.\n */\n techniqueCoverageMinimum?: TechniqueCoverageMinimumPolicy;\n /**\n * Issue #2070 — generator self-consistency sampling policy. Controls the\n * number of independently seeded generator samples emitted before the\n * runner either performs structural majority voting (`3`) or preserves the\n * legacy single-sample flow (`1`).\n *\n * Optional for backwards compatibility: when omitted the runner falls back\n * to the secure default of `3` for `eu-banking-default`, but the runtime\n * silently degrades to `1` when the active generator deployment does not\n * declare seed support.\n */\n selfConsistency?: {\n readonly sampleCount: 1 | 3;\n };\n /**\n * Issue #2116 — faithfulness-tier-elastic fallback strictness.\n *\n * Drives how the cross-modal-faithfulness gate treats verdicts that\n * lack `stepVerdicts` (legacy schema 1.0.0 producers, refused\n * cross-family judge that still emitted a case-level score, etc.).\n *\n * - `false` (the secure default for `eu-banking-default`):\n * `case_level_fallback` raises a job-level **warning**\n * (`policy:cross-modal-faithfulness:case-level-fallback`) and the\n * case-level score continues to drive the threshold check. The\n * evaluation mode is recorded on\n * `TestCasePolicyReport.faithfulnessEvaluation` for audit.\n * - `true`: `case_level_fallback` is treated as an **error**, mirroring\n * the per-step strictness. Operators that require per-step audit\n * evidence flip this on so a verdict with no per-step claims fails\n * the run rather than slipping through under the legacy fallback.\n *\n * The companion `policy:cross-modal-faithfulness:evaluation-missing`\n * outcome is always raised at error severity when no faithfulness\n * verdict is present at all — that path is not configurable because\n * \"no judge ran\" must never silently pass an audited gate.\n *\n * Optional for backwards compatibility: when omitted the runtime\n * applies the `false` default.\n */\n requirePerStepFaithfulness?: boolean;\n /**\n * Issue #2169 — policy-scoped elastic FinOps wall-clock coefficients for\n * the `test_generation` role.\n *\n * Optional for backwards compatibility: when omitted the runtime falls back\n * to the built-in `eu-banking-default` coefficients.\n */\n finopsWallClockBudget?: FinOpsWallClockBudgetPolicy;\n /**\n * Issue #2177 — allow-list of attested hosting regions accepted by the\n * `G8_EU_REGION_ATTESTED` hard gate. Optional for backwards compatibility:\n * when omitted the runtime falls back to the profile's built-in default.\n */\n allowedHostingRegions?: readonly RegionAttestationHostingRegion[];\n /**\n * Issue #2128 — opt-in training-influence DP budget configuration. When\n * omitted or `.enabled === false` (the secure default), the accountant\n * is inactive.\n *\n * This field is the policy-profile-side declaration of the budget. The\n * runtime helpers in `src/test-intelligence/training-influence-dp-budget.ts`\n * (`applyDpCharge`, `buildDpBudgetConsumedManifest`, ...) are the\n * call-site API operators wire into their gateway adapter to charge each\n * job, block on cap exhaustion, and emit the per-job\n * `dp-budget-consumed.json` artifact for audit replay. The harness\n * itself does not call these helpers — this is a library surface, not\n * an automatically-applied gate.\n *\n * NOT a cryptographic DP guarantee — this is an accounting layer that\n * supports operator decision-making. See the ADR for the model.\n */\n trainingInfluenceDpBudget?: TrainingInfluenceDpBudgetConfig;\n}\n\nexport const ALLOWED_JUDGE_REFUSAL_POLICIES = [\n \"fail_open\",\n \"fail_closed\",\n \"needs_review\",\n] as const;\n\nexport type JudgeRefusalPolicy =\n (typeof ALLOWED_JUDGE_REFUSAL_POLICIES)[number];\n\nexport interface JudgeRefusalPolicyConfig {\n faithfulness: JudgeRefusalPolicy;\n a11y: JudgeRefusalPolicy;\n}\n\n/** Built-in policy profile shape. Profiles are identified by `id`+`version`. */\nexport interface TestCasePolicyProfile {\n id: string;\n version: string;\n description: string;\n rules: TestCasePolicyProfileRules;\n}\n\n/** Per-element coverage breakdown. */\nexport interface TestCaseCoverageBucket {\n /** Total IR elements of this kind across the job. */\n total: number;\n /** Element ids covered by at least one accepted test case. */\n covered: number;\n /** Coverage ratio in [0, 1]; 0 when total=0 (no elements => no gap). */\n ratio: number;\n /** Element ids that have no covering test case. */\n uncoveredIds: string[];\n}\n\n/** Coverage/quality signals across one job's generated test cases. */\nexport interface TestCaseCoverageReport {\n schemaVersion: typeof TEST_CASE_COVERAGE_REPORT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n generatedAt: string;\n jobId: string;\n policyProfileId: string;\n totalTestCases: number;\n fieldCoverage: TestCaseCoverageBucket;\n actionCoverage: TestCaseCoverageBucket;\n fieldLifecycleCoverage: TestCaseCoverageBucket;\n /**\n * Issue #2168 — share of `recommended_positive_path` field-lifecycle\n * transitions that are exercised by at least one accepted test step.\n * Optional (omitted when the workflow topology declares no field\n * lifecycles or no recommended-tier transitions) so the byte shape of\n * the coverage report stays stable for runs that pre-date the\n * tier-aware validator.\n */\n recommendedTransitionCoverage?: TestCaseCoverageBucket;\n validationCoverage: TestCaseCoverageBucket;\n navigationCoverage: TestCaseCoverageBucket;\n /**\n * Share of structured acceptance criteria covered by at least one generated\n * case via `qualitySignals.coveredRequirementIds`.\n *\n * Optional for backwards-compatible artifact readers; new runs include it\n * whenever the CoveragePlan carries `acceptance_criterion` requirements.\n */\n acceptanceCriteriaCoverage?: TestCaseCoverageBucket;\n traceCoverage: { total: number; withTrace: number; ratio: number };\n negativeCaseCount: number;\n validationCaseCount: number;\n boundaryCaseCount: number;\n accessibilityCaseCount: number;\n workflowCaseCount: number;\n positiveCaseCount: number;\n /** Avg assumptions per case. */\n assumptionsRatio: number;\n /** Total open questions across all cases. */\n openQuestionsCount: number;\n /** Test-case pairs sharing >= duplicate threshold. */\n duplicatePairs: TestCaseDuplicatePair[];\n /** Optional 0..1 rubric score from a downstream rater (Wave 2). */\n rubricScore?: number;\n /**\n * Domain-invariant coverage (Issue #2040). Reports the share of\n * registered invariants exercised by at least one accepted test case.\n * `total` is the registered invariant count, `exercised` is the count\n * matched by `forall` for at least one case, and `ratio` is\n * `exercised / total` rounded to six digits (0 when `total === 0`).\n */\n invariantCoverage?: {\n total: number;\n exercised: number;\n ratio: number;\n /** Sorted invariant ids registered for the run. */\n registeredIds: string[];\n /** Sorted invariant ids exercised by at least one accepted case. */\n exercisedIds: string[];\n };\n /**\n * Per-case invariant exercise mapping (Issue #2040). Sorted alphabetically\n * by `testCaseId`; only cases that exercise at least one invariant appear\n * in the array. Surfaces the `exercises: [\"INV-VAT-01\", ...]` annotation\n * the issue spec requires without altering the strict\n * `GeneratedTestCase` schema.\n */\n invariantAnnotations?: TestCaseInvariantAnnotation[];\n}\n\n/**\n * Per-case invariant annotation row (Issue #2040). Each entry lists the\n * invariant ids the test case is in scope for (i.e. `forall === true`),\n * deduplicated and alphabetically sorted.\n */\nexport interface TestCaseInvariantAnnotation {\n testCaseId: string;\n exercises: string[];\n}\n\n/* ------------------------------------------------------------------ */\n/* Self-verify rubric pass (Issue #1379) */\n/* ------------------------------------------------------------------ */\n\n/**\n * Allowed scoring dimensions evaluated by the self-verify rubric pass\n * (Issue #1379). Each dimension is scored in `[0, 1]` per test case;\n * the per-case rubric score is the arithmetic mean of the supplied\n * dimensions (and visual subscores when present). The discriminant is\n * the runtime source of truth — adding a new dimension is a minor\n * (additive) bump per the contract versioning rules.\n */\nexport const ALLOWED_SELF_VERIFY_RUBRIC_DIMENSIONS = [\n \"schema_conformance\",\n \"source_trace_completeness\",\n \"assumption_open_question_marking\",\n \"expected_result_coverage\",\n \"negative_boundary_presence\",\n \"duplication_flag_consistency\",\n] as const;\n\n/** Single rubric scoring dimension. */\nexport type SelfVerifyRubricDimension =\n (typeof ALLOWED_SELF_VERIFY_RUBRIC_DIMENSIONS)[number];\n\n/**\n * Allowed multimodal visual subscores layered onto the rubric pass when\n * a validated `VisualScreenDescription` batch is supplied alongside the\n * test cases (Issue #1379, multimodal addendum 2026-04-24). The four\n * subscores are: visible-control coverage, state/validation coverage,\n * ambiguity handling, and the unsupported-visual-claims penalty (the\n * latter is interpreted as `1 - penalty` so all subscores remain in\n * `[0, 1]` where higher is better).\n */\nexport const ALLOWED_SELF_VERIFY_RUBRIC_VISUAL_SUBSCORES = [\n \"visible_control_coverage\",\n \"state_validation_coverage\",\n \"ambiguity_handling\",\n \"unsupported_visual_claims\",\n] as const;\n\n/** Single multimodal visual subscore kind. */\nexport type SelfVerifyRubricVisualSubscoreKind =\n (typeof ALLOWED_SELF_VERIFY_RUBRIC_VISUAL_SUBSCORES)[number];\n\n/**\n * Allowed refusal codes reported by the self-verify rubric pass when the\n * pass cannot publish a complete per-case evaluation. The code is\n * load-bearing: callers that gate on rubric output check this code and\n * fall back to the unscored coverage path. No two refusal codes overlap.\n */\nexport const ALLOWED_SELF_VERIFY_RUBRIC_REFUSAL_CODES = [\n \"feature_disabled\",\n \"gateway_failure\",\n \"model_binding_mismatch\",\n \"schema_invalid_response\",\n \"score_out_of_range\",\n \"missing_test_case_score\",\n \"extra_test_case_score\",\n \"duplicate_test_case_score\",\n \"image_payload_attempted\",\n] as const;\n\n/** Single rubric pass refusal classification. */\nexport type SelfVerifyRubricRefusalCode =\n (typeof ALLOWED_SELF_VERIFY_RUBRIC_REFUSAL_CODES)[number];\n\n/** Single dimension score in the persisted rubric report. */\nexport interface SelfVerifyRubricDimensionScore {\n dimension: SelfVerifyRubricDimension;\n /** Score in `[0, 1]`; rounded to 6 digits in the persisted artifact. */\n score: number;\n}\n\n/** Single visual subscore in the persisted rubric report. */\nexport interface SelfVerifyRubricVisualSubscore {\n subscore: SelfVerifyRubricVisualSubscoreKind;\n /** Score in `[0, 1]`; rounded to 6 digits in the persisted artifact. */\n score: number;\n}\n\n/**\n * Short, structured rule citation attached to a per-case evaluation. The\n * citation surfaces the rubric rule the rater applied and a short\n * audit-grade message. No chain-of-thought is persisted — `message` is\n * a single sentence the rater produced when grading the case.\n */\nexport interface SelfVerifyRubricRuleCitation {\n /** Stable rule identifier (e.g. `\"schema_conformance.required_fields\"`). */\n ruleId: string;\n /** Audit-grade short message; sanitized + truncated by the parser. */\n message: string;\n}\n\n/** Per-test-case rubric evaluation row. */\nexport interface SelfVerifyRubricCaseEvaluation {\n testCaseId: string;\n /** Sorted by dimension name for byte stability. */\n dimensions: SelfVerifyRubricDimensionScore[];\n /** Visual subscores when the rubric pass had a visual sidecar input. */\n visualSubscores?: SelfVerifyRubricVisualSubscore[];\n /** Sorted by `ruleId` for byte stability. Empty array when no rule fired. */\n citations: SelfVerifyRubricRuleCitation[];\n /**\n * Aggregate per-case rubric score in `[0, 1]`. Arithmetic mean of the\n * dimensions and visual subscores; rounded to 6 digits in the artifact.\n */\n rubricScore: number;\n}\n\n/** Job-level aggregate of the rubric pass. */\nexport interface SelfVerifyRubricAggregateScores {\n /** Mean of the per-case `rubricScore` values across the job. */\n jobLevelRubricScore: number;\n /** Job-level mean per rubric dimension; sorted by dimension name. */\n dimensionScores: SelfVerifyRubricDimensionScore[];\n /** Job-level mean per visual subscore when the rubric pass scored visuals. */\n visualSubscores?: SelfVerifyRubricVisualSubscore[];\n}\n\n/** Refusal record emitted when the rubric pass cannot publish scores. */\nexport interface SelfVerifyRubricRefusal {\n code: SelfVerifyRubricRefusalCode;\n /** Sanitized + truncated message; no secrets, no chain-of-thought. */\n message: string;\n}\n\n/**\n * Persisted self-verify rubric pass artifact (Issue #1379).\n *\n * Sibling to `validation-report.json` and `coverage-report.json` under\n * `<runDir>/testcases/self-verify-rubric.json`. Always byte-stable: per\n * case evaluations are sorted by `testCaseId`, dimension lists are\n * sorted by dimension name, and citations are sorted by rule id.\n *\n * When a `refusal` is present, `caseEvaluations` is empty and the\n * `aggregate` carries `0` job/dimension scores; downstream policy gates\n * MUST treat the refusal as a soft signal (it does not by itself block\n * a job) and surface it on the inspector for operator review.\n */\nexport interface SelfVerifyRubricReport {\n schemaVersion: typeof SELF_VERIFY_RUBRIC_REPORT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n promptTemplateVersion: typeof SELF_VERIFY_RUBRIC_PROMPT_TEMPLATE_VERSION;\n generatedAt: string;\n jobId: string;\n policyProfileId: string;\n /** Whether the rubric replay cache served the result without invoking the LLM. */\n cacheHit: boolean;\n /** Hex-encoded SHA-256 digest of the rubric replay-cache key. */\n cacheKeyDigest: string;\n /** Identity stamps of the deployment that produced (or would have produced) the scores. */\n modelDeployment: string;\n modelRevision: string;\n gatewayRelease: string;\n /** Set when the pass refused to publish scores. */\n refusal?: SelfVerifyRubricRefusal;\n /** Sorted by `testCaseId` for byte stability. Empty when `refusal` is set. */\n caseEvaluations: SelfVerifyRubricCaseEvaluation[];\n aggregate: SelfVerifyRubricAggregateScores;\n}\n\n/**\n * Replay-cache key for the self-verify rubric pass. The key carries a\n * hard discriminator (`passKind`) so it can never collide with the\n * test-generation replay cache key, even when other identity fields\n * happen to match.\n */\nexport interface SelfVerifyRubricReplayCacheKey {\n passKind: \"self_verify_rubric\";\n /** SHA-256 of the rubric input (test cases + intent + visual descriptions). */\n inputHash: string;\n /** SHA-256 of the rubric prompt + response schema identity. */\n promptHash: string;\n /** SHA-256 of the rubric response JSON schema. */\n schemaHash: string;\n /** Deployment identity used for the rubric pass. */\n modelDeployment: string;\n /** Gateway compatibility mode; Issue #1379 pins this to `openai_chat`. */\n compatibilityMode: LlmGatewayCompatibilityMode;\n modelRevision: string;\n gatewayRelease: string;\n policyBundleVersion: string;\n redactionPolicyVersion: typeof REDACTION_POLICY_VERSION;\n promptTemplateVersion: typeof SELF_VERIFY_RUBRIC_PROMPT_TEMPLATE_VERSION;\n rubricSchemaVersion: typeof SELF_VERIFY_RUBRIC_REPORT_SCHEMA_VERSION;\n seed?: number;\n}\n\n/** Stored cache entry for a rubric report. */\nexport interface SelfVerifyRubricReplayCacheEntry {\n key: string;\n storedAt: string;\n report: SelfVerifyRubricReport;\n}\n\n/** Cache lookup outcome consumed by the rubric pass orchestration layer. */\nexport type SelfVerifyRubricReplayCacheLookupResult =\n | { hit: true; entry: SelfVerifyRubricReplayCacheEntry }\n | { hit: false; key: string };\n\n/** Pair of generated test case ids exceeding the similarity threshold. */\nexport interface TestCaseDuplicatePair {\n leftTestCaseId: string;\n rightTestCaseId: string;\n similarity: number;\n}\n\n/**\n * Polarity surfaced by a generated test case's oracle (Issue #2123).\n *\n * The equivalence-class fingerprint deliberately collapses the persisted\n * polarity / category / type triple into the coarsest semantic axis\n * relevant for redundancy: whether the case's oracle expects the system\n * to ACCEPT the input (positive), REJECT it (negative / validation), test\n * a value at a partition boundary (boundary), advance through the\n * navigation graph (navigation), or assert an accessibility property\n * (accessibility). Cases with identical covered ids, technique, and risk\n * class but different `oraclePolarity` are NOT considered redundant.\n */\nexport const ALLOWED_TEST_CASE_ORACLE_POLARITIES = [\n \"positive\",\n \"negative\",\n \"boundary\",\n \"navigation\",\n \"accessibility\",\n] as const;\nexport type TestCaseOraclePolarity =\n (typeof ALLOWED_TEST_CASE_ORACLE_POLARITIES)[number];\n\n/**\n * Semantic equivalence-class fingerprint (Issue #2123).\n *\n * Derived from `(coveredFieldIds, coveredActionIds, riskClass, technique,\n * oraclePolarity)` — NOT from text. Two generated cases are considered\n * to belong to the same equivalence class iff their\n * {@link EquivalenceClassFingerprint} fields are byte-equal under the\n * canonical projection (`coveredFieldIds` and `coveredActionIds` sorted\n * lexically and de-duplicated). The fingerprint is the primary signal\n * the validator uses to detect redundancy WITHIN a technique bucket:\n * the previous Levenshtein/Jaccard-only path missed cases that differed\n * by a few characters but covered the same equivalence class.\n */\nexport interface EquivalenceClassFingerprint {\n readonly coveredFieldIds: readonly string[];\n readonly coveredActionIds: readonly string[];\n readonly riskClass: TestCaseRiskCategory;\n readonly technique: TestCaseTechnique29119;\n readonly oraclePolarity: TestCaseOraclePolarity;\n}\n\n/**\n * Allowed visual-sidecar policy outcome codes (Issue #1364 / #1386).\n *\n * These mirror the visual-sidecar policy outcomes attached to the policy\n * report when the multimodal sidecar misbehaves or is downgraded.\n */\nexport const ALLOWED_VISUAL_SIDECAR_VALIDATION_OUTCOMES = [\n \"ok\",\n \"schema_invalid\",\n \"low_confidence\",\n \"fallback_used\",\n \"possible_pii\",\n \"prompt_injection_like_text\",\n \"conflicts_with_figma_metadata\",\n \"primary_unavailable\",\n] as const;\nexport type VisualSidecarValidationOutcome =\n (typeof ALLOWED_VISUAL_SIDECAR_VALIDATION_OUTCOMES)[number];\n\n/** Single per-screen visual-sidecar validation row. */\nexport interface VisualSidecarValidationRecord {\n screenId: string;\n deployment: SidecarDeployment;\n outcomes: VisualSidecarValidationOutcome[];\n /** Issues found while structurally validating the description. */\n issues: TestCaseValidationIssue[];\n /** Mean confidence reported by the sidecar (0..1). */\n meanConfidence: number;\n}\n\n/** Aggregate visual-sidecar validation report across a job. */\nexport interface VisualSidecarValidationReport {\n schemaVersion: typeof VISUAL_SIDECAR_VALIDATION_REPORT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n visualSidecarSchemaVersion: typeof VISUAL_SIDECAR_SCHEMA_VERSION;\n generatedAt: string;\n jobId: string;\n totalScreens: number;\n screensWithFindings: number;\n /** Whether any record carries a non-`ok`/non-`fallback_used` outcome that blocks generation. */\n blocked: boolean;\n records: VisualSidecarValidationRecord[];\n}\n\n/**\n * Allowed gateway roles. Each role is bound to a single deployment to keep the\n * structured test-case generator (`gpt-oss-120b`) strictly separated from the\n * multimodal visual sidecars (`llama-4-maverick-vision`, `phi-4-multimodal-instruct`),\n * and from the cross-model logic judge (Issue #1932) which reuses the structured-output\n * surface but is intentionally bound to a different deployment so a self-consistency\n * bias from the generator cannot be amplified by reusing the same model on the judge.\n */\nexport const ALLOWED_LLM_GATEWAY_ROLES = [\n \"test_generation\",\n \"visual_primary\",\n \"visual_fallback\",\n \"logic_judge\",\n \"a11y_judge\",\n \"coverage_planner\",\n \"risk_ranker\",\n] as const;\nexport type LlmGatewayRole = (typeof ALLOWED_LLM_GATEWAY_ROLES)[number];\n\n/**\n * Wire-protocol compatibility modes. `openai_chat` is the only mode shipped in\n * Wave 1; the array is the source of truth so future modes (`openai_responses`,\n * `custom_adapter`) plug in without changing the call sites.\n */\nexport const ALLOWED_LLM_GATEWAY_COMPATIBILITY_MODES = [\"openai_chat\"] as const;\nexport type LlmGatewayCompatibilityMode =\n (typeof ALLOWED_LLM_GATEWAY_COMPATIBILITY_MODES)[number];\n\n/** Authentication strategy for outbound requests to the LLM gateway. */\nexport const ALLOWED_LLM_GATEWAY_AUTH_MODES = [\n \"api_key\",\n \"bearer_token\",\n \"none\",\n] as const;\nexport type LlmGatewayAuthMode =\n (typeof ALLOWED_LLM_GATEWAY_AUTH_MODES)[number];\n\n/**\n * Wire-format strategy for structured outputs. Decouples our in-process\n * structured-output behaviour (the gateway always parses JSON content and\n * validates it against `responseSchema` when present) from the on-the-wire\n * `response_format` field shipped to the upstream provider.\n *\n * - `\"json_schema\"` (default) — emit\n * `response_format: { type: \"json_schema\", json_schema: {...} }` when the\n * client config declares `structuredOutputs: true` and the request carries\n * a schema. Matches OpenAI / Azure OpenAI Structured Outputs.\n * - `\"json_object\"` — emit `response_format: { type: \"json_object\" }`. The\n * schema is still validated in-process. Use for providers that accept the\n * weaker `json_object` mode but reject `json_schema`.\n * - `\"none\"` — omit `response_format` entirely. Use when the deployment\n * silently returns empty content for any `response_format` value (observed\n * on `gpt-oss-120b` via Azure AI Foundry's `openai/v1` path on 2026-05-02:\n * any `response_format` setting yields `content: \"\"` after burning ~2\n * tokens; with no `response_format`, the model produces clean parseable\n * JSON when the prompt instructs it to). The gateway still parses and\n * schema-validates the content in-process so the contract guarantee\n * (\"structured-output success returns parsed JSON\") is unchanged.\n */\nexport const ALLOWED_LLM_GATEWAY_WIRE_STRUCTURED_OUTPUT_MODES = [\n \"json_schema\",\n \"json_object\",\n \"none\",\n] as const;\nexport type LlmGatewayWireStructuredOutputMode =\n (typeof ALLOWED_LLM_GATEWAY_WIRE_STRUCTURED_OUTPUT_MODES)[number];\n\n/**\n * Internal constrained-decoding adapters. These are more expressive than the\n * wire-mode enum because multiple provider integrations can implement the same\n * logical schema-constrained contract.\n */\nexport const ALLOWED_LLM_CONSTRAINED_DECODING_ADAPTER_IDS = [\n \"openai_json_schema\",\n \"openai_json_object\",\n \"prompt_only\",\n \"outlines\",\n \"llguidance\",\n] as const;\nexport type LlmConstrainedDecodingAdapterId =\n (typeof ALLOWED_LLM_CONSTRAINED_DECODING_ADAPTER_IDS)[number];\n\n/** How strongly the selected constrained-decoding adapter enforces shape. */\nexport const ALLOWED_LLM_CONSTRAINED_DECODING_ENFORCEMENTS = [\n \"provider\",\n \"sampler\",\n \"prompt_only\",\n] as const;\nexport type LlmConstrainedDecodingEnforcement =\n (typeof ALLOWED_LLM_CONSTRAINED_DECODING_ENFORCEMENTS)[number];\n\n/**\n * Operator-configured constrained-decoding preference. The gateway resolves\n * this preference into an actual adapter per request and may explicitly fall\n * back when the compatibility mode or deployment cannot honor it.\n */\nexport interface LlmConstrainedDecodingConfig {\n /** Preferred adapter for schema-carrying requests. */\n preferredAdapter: LlmConstrainedDecodingAdapterId;\n /**\n * Optional explicit fallback adapter. Defaults to `prompt_only` when\n * omitted; callers should keep this narrow so a single request never fan-outs\n * across multiple extra network attempts unexpectedly.\n */\n fallbackAdapter?: LlmConstrainedDecodingAdapterId;\n /**\n * Optional adapter-version pin surfaced in artifacts and FinOps so operators\n * can correlate cost/quality shifts with constrained-decoding rollout.\n */\n adapterVersion?: string;\n}\n\n/**\n * Resolved constrained-decoding metadata for one request attempt. This is\n * attached to gateway results and propagated into FinOps/agent-participation\n * artifacts so fallback behavior is auditable.\n */\nexport interface LlmConstrainedDecodingMetadata {\n /** True when the caller supplied a response schema on the request. */\n requested: boolean;\n /** Adapter actually selected for this attempt. */\n adapterId: LlmConstrainedDecodingAdapterId;\n /** Enforcement class of the selected adapter. */\n enforcement: LlmConstrainedDecodingEnforcement;\n /** Wire-mode emitted to the upstream compatibility layer. */\n wireMode: LlmGatewayWireStructuredOutputMode;\n /** True when the preferred adapter could not be used and a fallback ran. */\n fallback: boolean;\n /** Redacted reason for fallback, when applicable. */\n fallbackReason?: string;\n /** Optional adapter-version pin from operator config. */\n adapterVersion?: string;\n}\n\n/**\n * Disjoint failure classes surfaced by `LlmGatewayClient.generate`. Refusals,\n * schema-invalid responses, and image-payload guard rejections are NOT\n * retryable; transport, timeout, and rate-limit failures are.\n */\nexport const ALLOWED_LLM_GATEWAY_ERROR_CLASSES = [\n \"refusal\",\n \"schema_invalid\",\n \"incomplete\",\n \"timeout\",\n \"rate_limited\",\n \"transport\",\n \"image_payload_rejected\",\n \"input_budget_exceeded\",\n \"response_too_large\",\n // Issue #1703 (audit-2026-05 Wave 2): protocol-level failure distinct from\n // schema_invalid. Covers HTTP status semantics that indicate gateway/auth\n // misconfiguration rather than model-output schema violations (401 / 403,\n // and other auth/routing rejections). Not retryable — operator action\n // required (rotate key, fix endpoint, repair scope).\n \"protocol\",\n // Issue #1694 (audit-2026-05 Wave 2): caller-initiated cancellation via\n // upstream AbortSignal. Distinct from \"timeout\" so the breaker does not\n // record cancellation as a transient failure (and the retry policy does\n // not bounce it). Not retryable.\n \"canceled\",\n] as const;\nexport type LlmGatewayErrorClass =\n (typeof ALLOWED_LLM_GATEWAY_ERROR_CLASSES)[number];\n\n/**\n * Capability flags declared by the gateway operator and verified at probe\n * time. Streaming is disabled by default in Wave 1: the test-generation\n * pipeline consumes only the final structured JSON envelope.\n */\nexport interface LlmGatewayCapabilities {\n structuredOutputs: boolean;\n seedSupport: boolean;\n reasoningEffortSupport: boolean;\n maxOutputTokensSupport: boolean;\n streamingSupport: boolean;\n imageInputSupport: boolean;\n}\n\n/** Per-capability probe verdict carried in the persisted artifact. */\nexport type LlmCapabilityProbeOutcome =\n | \"supported\"\n | \"unsupported\"\n | \"untested\"\n | \"probe_failed\";\n\n/** Probe rows can cover declared capability flags plus the mandatory text-chat baseline. */\nexport type LlmCapabilityProbeCapability =\n | keyof LlmGatewayCapabilities\n | \"textChat\";\n\n/** One probe row in `llm-capabilities.json`. */\nexport interface LlmCapabilityProbeRecord {\n capability: LlmCapabilityProbeCapability;\n declared: boolean;\n outcome: LlmCapabilityProbeOutcome;\n detail?: string;\n}\n\n/**\n * Persistable capabilities artifact. Contains identity (role, deployment,\n * gateway release, model revision, optional model-weights SHA-256) and the\n * declared/observed capabilities. NEVER contains tokens, headers, or\n * reasoning traces.\n */\nexport interface LlmCapabilitiesArtifact {\n schemaVersion: typeof LLM_CAPABILITIES_SCHEMA_VERSION;\n contractVersion: typeof LLM_GATEWAY_CONTRACT_VERSION;\n generatedAt: string;\n jobId: string;\n role: LlmGatewayRole;\n compatibilityMode: LlmGatewayCompatibilityMode;\n deployment: string;\n modelRevision: string;\n gatewayRelease: string;\n modelWeightsSha256?: string;\n capabilities: LlmGatewayCapabilities;\n probes: LlmCapabilityProbeRecord[];\n}\n\n/** Tunable circuit-breaker thresholds for an LLM gateway client. */\nexport interface LlmGatewayCircuitBreakerConfig {\n failureThreshold: number;\n resetTimeoutMs: number;\n}\n\nexport type LlmCircuitState = \"closed\" | \"open\" | \"half_open\";\n\n/**\n * Construction-time configuration for an LLM gateway client.\n *\n * API tokens are NEVER in this object. Operators inject a token reader via\n * the runtime factory; the reader is invoked once per request and the value\n * is held only for the duration of that request.\n */\nexport interface LlmGatewayClientConfig {\n role: LlmGatewayRole;\n compatibilityMode: LlmGatewayCompatibilityMode;\n baseUrl: string;\n deployment: string;\n modelRevision: string;\n gatewayRelease: string;\n /** Optional operator-configured ICT register reference for this deployment. */\n ictRegisterRef?: string;\n modelWeightsSha256?: string;\n authMode: LlmGatewayAuthMode;\n declaredCapabilities: LlmGatewayCapabilities;\n timeoutMs: number;\n maxRetries: number;\n circuitBreaker: LlmGatewayCircuitBreakerConfig;\n /**\n * Hard upper bound on the gateway response body, in bytes. The transport\n * counts decoded bytes during read and aborts the stream the moment the\n * running total exceeds this cap; the failure surfaces as\n * `errorClass: \"response_too_large\"` with `retryable: false` (Issue #1414).\n * The cap is enforced both via the `Content-Length` header (pre-read\n * short-circuit) and via streaming byte accounting (so a missing or\n * mendacious header still cannot exhaust memory). Defaults to\n * `8 * 1024 * 1024` (8 MiB) when omitted; positive integer values up to\n * `Number.MAX_SAFE_INTEGER` are accepted, anything else throws at\n * client construction. The mock gateway has no transport and ignores\n * this field.\n */\n maxResponseBytes?: number;\n /**\n * Wire-format strategy for structured outputs. Defaults to `\"json_schema\"`\n * (preserves existing behaviour). Set to `\"json_object\"` for providers\n * that accept the weaker mode but reject `json_schema`. Set to `\"none\"`\n * for providers that return empty content for ANY `response_format`\n * (observed on Azure AI Foundry's `gpt-oss-120b` via the `openai/v1`\n * path on 2026-05-02). In all three modes, the gateway parses and\n * validates the response content as JSON in-process when the request\n * carries a `responseSchema`, so the contract guarantee surfaced to\n * callers is unchanged. See {@link LlmGatewayWireStructuredOutputMode}.\n */\n wireStructuredOutputMode?: LlmGatewayWireStructuredOutputMode;\n /**\n * Optional internal constrained-decoding preference. When omitted, the\n * gateway derives a backward-compatible adapter from\n * `wireStructuredOutputMode` (`json_schema` -> `openai_json_schema`,\n * `json_object` -> `openai_json_object`, `none` -> `prompt_only`).\n */\n constrainedDecoding?: LlmConstrainedDecodingConfig;\n /**\n * Per-modality token-estimation strategy used by the client-side input\n * budget guard (Issue #1930). Defaults to\n * {@link DEFAULT_LLM_IMAGE_TOKEN_STRATEGY} (`\"openai_tiles\"`) when omitted.\n * The wire payload is unaffected: the strategy only changes how the gateway\n * estimates `imageInputs` against\n * {@link LlmGenerationRequest.maxInputTokens}, so a Visual-Sidecar request\n * carrying a 119 KiB / 1280×720 PNG no longer pre-flight-rejects under the\n * production-default `visual_primary.maxInputTokensPerRequest = 40_000`.\n */\n imageTokenStrategy?: LlmImageTokenStrategy;\n}\n\n/** Image payload accepted by visual sidecars. Rejected for `test_generation`. */\nexport interface LlmImageInput {\n mimeType: string;\n base64Data: string;\n /**\n * Decoded pixel width of the image. When present together with\n * {@link LlmImageInput.heightPx} the prompt-size estimator can use a\n * tile-based formula (Issue #1930) instead of charging the raw base64\n * byte length against the token budget. The base64 string remains the\n * canonical wire payload; these fields only inform the estimator.\n */\n widthPx?: number;\n /** Decoded pixel height. Paired with {@link LlmImageInput.widthPx}. */\n heightPx?: number;\n}\n\n/** Reasoning-effort hint forwarded only when `reasoningEffortSupport` is true. */\nexport type LlmReasoningEffort = \"low\" | \"medium\" | \"high\";\n\n/**\n * Per-modality token-estimation strategy for image inputs (Issue #1930).\n *\n * - `openai_tiles`: OpenAI Chat-Vision aligned default. Charges\n * `ceil(widthPx*heightPx / {@link LLM_IMAGE_OPENAI_TILE_SIZE_PX}^2) *\n * {@link LLM_IMAGE_OPENAI_TOKENS_PER_TILE} +\n * {@link LLM_IMAGE_OPENAI_BASE_TOKENS}` tokens per image.\n * - `llama_tiles`: Llama-Vision aligned default. Charges\n * `ceil(widthPx*heightPx / {@link LLM_IMAGE_LLAMA_TILE_SIZE_PX}^2) *\n * {@link LLM_IMAGE_LLAMA_TOKENS_PER_TILE} +\n * {@link LLM_IMAGE_LLAMA_BASE_TOKENS}` tokens per image.\n * - `raw_bytes`: legacy behaviour — charges `ceil(base64.length / 4)` tokens.\n * Retained as an explicit override for providers whose multimodal billing\n * actually scales with payload size, and as the fallback for any image\n * whose pixel dimensions are not supplied.\n */\nexport const ALLOWED_LLM_IMAGE_TOKEN_STRATEGIES = [\n \"openai_tiles\",\n \"llama_tiles\",\n \"raw_bytes\",\n] as const;\n\nexport type LlmImageTokenStrategy =\n (typeof ALLOWED_LLM_IMAGE_TOKEN_STRATEGIES)[number];\n\n/**\n * Default per-modality strategy used when a gateway client does not pin\n * `imageTokenStrategy` in its `LlmGatewayClientConfig`. OpenAI-aligned tiles\n * keep the FinOps envelope realistic for the most common deployments and\n * remain a safe upper bound for providers whose actual multimodal token cost\n * is lower than tile + base.\n */\nexport const DEFAULT_LLM_IMAGE_TOKEN_STRATEGY: LlmImageTokenStrategy =\n \"openai_tiles\";\n\n/** Tile edge length (px) for the OpenAI Chat-Vision token estimate. */\nexport const LLM_IMAGE_OPENAI_TILE_SIZE_PX = 512 as const;\n/** Tokens charged per OpenAI Chat-Vision tile (high-detail). */\nexport const LLM_IMAGE_OPENAI_TOKENS_PER_TILE = 85 as const;\n/** Constant base tokens added once per OpenAI Chat-Vision image. */\nexport const LLM_IMAGE_OPENAI_BASE_TOKENS = 85 as const;\n\n/** Tile edge length (px) for the Llama-Vision token estimate. */\nexport const LLM_IMAGE_LLAMA_TILE_SIZE_PX = 560 as const;\n/**\n * Tokens charged per Llama-Vision tile. Tracks the published\n * Llama-3.2-Vision / Llama-4-Maverick-Vision tile encoding (CLS + 1600\n * patches per tile at 14 px patch size).\n */\nexport const LLM_IMAGE_LLAMA_TOKENS_PER_TILE = 1601 as const;\n/** Constant base tokens added once per Llama-Vision image. */\nexport const LLM_IMAGE_LLAMA_BASE_TOKENS = 1 as const;\n\n/** Fixed bytes-per-token estimator used by the shared prompt-size heuristic. */\nexport const CONTEXT_BUDGET_ESTIMATOR_BYTES_PER_TOKEN = 4 as const;\n\n/** Schema version for persisted context-budget analyzer reports. */\nexport const CONTEXT_BUDGET_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical directory for per-role-step context-budget artifacts. */\nexport const CONTEXT_BUDGET_ARTIFACT_DIRECTORY = \"context-budget\" as const;\n\n/** Deterministic breach actions surfaced by the context-budget analyzer. */\nexport const ALLOWED_CONTEXT_BUDGET_ACTIONS = [\n \"none\",\n \"compact_prompt_payload\",\n \"drop_optional_context\",\n \"needs_review\",\n] as const;\n\nexport type ContextBudgetAction =\n (typeof ALLOWED_CONTEXT_BUDGET_ACTIONS)[number];\n\n/** Stable category kinds consumed by the context-budget analyzer. */\nexport const ALLOWED_CONTEXT_BUDGET_CATEGORY_KINDS = [\n \"system_instructions\",\n \"business_intent_ir\",\n \"visual_binding\",\n \"source_context\",\n \"coverage_plan\",\n \"risk_priorities\",\n \"generated_cases\",\n \"validation_findings\",\n \"judge_findings\",\n \"repair_history\",\n] as const;\n\nexport type ContextBudgetCategoryKind =\n (typeof ALLOWED_CONTEXT_BUDGET_CATEGORY_KINDS)[number];\n\n/** Priority drives which categories may be dropped during budget handling. */\nexport const ALLOWED_CONTEXT_BUDGET_PRIORITIES = [\n \"required\",\n \"important\",\n \"optional\",\n] as const;\n\nexport type ContextBudgetPriority =\n (typeof ALLOWED_CONTEXT_BUDGET_PRIORITIES)[number];\n\n/** Final disposition of a category after budget analysis. */\nexport const ALLOWED_CONTEXT_BUDGET_CATEGORY_STATUSES = [\n \"included\",\n \"compacted\",\n \"dropped\",\n] as const;\n\nexport type ContextBudgetCategoryStatus =\n (typeof ALLOWED_CONTEXT_BUDGET_CATEGORY_STATUSES)[number];\n\n/** Reported metadata for one analyzed category. */\nexport interface ContextBudgetCategory {\n readonly kind: ContextBudgetCategoryKind;\n readonly priority: ContextBudgetPriority;\n readonly estimatedTokens: number;\n readonly status: ContextBudgetCategoryStatus;\n readonly artifactHashes: readonly string[];\n}\n\n/** Deterministic per-role-step context-budget analyzer report. */\nexport interface ContextBudgetReport {\n readonly schemaVersion: typeof CONTEXT_BUDGET_REPORT_SCHEMA_VERSION;\n readonly jobId: string;\n readonly roleStepId: string;\n readonly parentJobId?: string;\n readonly roleLineageDepth?: number;\n readonly modelBinding: string;\n readonly maxInputTokens: number;\n readonly estimatedInputTokens: number;\n readonly categories: readonly ContextBudgetCategory[];\n readonly action: ContextBudgetAction;\n readonly compactedFromArtifactHashes: readonly string[];\n}\n\n/** Wire-shaped request handed to a gateway client. */\nexport interface LlmGenerationRequest {\n jobId: string;\n systemPrompt: string;\n userPrompt: string;\n responseSchema?: Record<string, unknown>;\n responseSchemaName?: string;\n imageInputs?: ReadonlyArray<LlmImageInput>;\n seed?: number;\n reasoningEffort?: LlmReasoningEffort;\n /**\n * Optional client-side input-token budget. Gateway clients estimate the\n * outgoing prompt size (system + user prompt + structured-output schema +\n * any image payloads) and reject the request before transport with\n * `errorClass: \"input_budget_exceeded\"` (`retryable: false`) when the\n * estimate exceeds this cap (Issue #1415). Operators set the cap to bound\n * cost and to keep maliciously expanded Figma metadata from reaching the\n * gateway. Negative or non-integer values are rejected as `schema_invalid`.\n */\n maxInputTokens?: number;\n maxOutputTokens?: number;\n /**\n * Optional per-request wall-clock budget. When set, the request times out\n * after `maxWallClockMs` instead of the client config's `timeoutMs` if\n * smaller, AND the resulting timeout failure is surfaced with\n * `retryable: false` (FinOps fail-closed semantics — Issue #1371).\n */\n maxWallClockMs?: number;\n /**\n * Optional per-request retry cap. When set, the gateway uses\n * `min(config.maxRetries, request.maxRetries)` so an operator can bound\n * retry blast radius for an individual job without rebuilding the client\n * (Issue #1371).\n */\n maxRetries?: number;\n /**\n * Optional caller-side `AbortSignal`. When the orchestrator cancels a\n * running job (#1694), this signal is plumbed all the way to the\n * outbound `fetch` so the in-flight LLM call is aborted immediately\n * instead of running until the per-request timeout fires. Aborts via\n * this signal surface as `errorClass: \"canceled\"` (`retryable: false`),\n * distinct from `\"timeout\"` so circuit-breaker accounting and retry\n * policy do not treat user cancellation as a transient transport\n * failure.\n */\n abortSignal?: AbortSignal;\n /**\n * Optional gateway-side idempotency inputs (Issue #1784). When set\n * AND the gateway runtime is wired with an idempotency cache, the\n * gateway computes the HMAC of these inputs (using the cache's\n * operator-configured secret) and consults the TTL-bounded cache\n * before dispatch. A cache hit returns the previously-completed\n * structured success result without making a second LLM call and\n * counts as `gateway_idempotent_replay` in FinOps (separate from\n * `replay_cache_hit`). The HMAC is never persisted to artifacts or\n * logs in plaintext; the cache file is stored under\n * `<runDir>/agent/idempotency-cache/<hmac>.json` with redacted bodies.\n */\n idempotency?: GatewayIdempotencyInputs;\n /**\n * Optional in-flight dedup key (Issue #1788). When supplied, one\n * `LlmGatewayClient` collapses concurrent requests with the same\n * `(promptHash, modelBinding, schemaHash, policyProfileHash)` into a\n * single Promise. Only in-flight calls dedupe here; completed-result\n * memoization remains a separate concern.\n *\n * `policyProfileHash` scopes the key so two tenants with identical prompt\n * + schema hashes never collide.\n */\n inFlightDedup?: GatewayInFlightDedupInputs;\n}\n\n/**\n * Optional caller-supplied key used by one gateway client instance to\n * collapse concurrent equivalent requests onto the same in-flight Promise\n * (Issue #1788).\n */\nexport interface GatewayInFlightDedupInputs {\n /** Optional FinOps source label credited with the dedup hit. */\n readonly source?: AgentSourceLabel;\n /** sha256 hex digest of the canonical request input payload. */\n readonly inputHash: string;\n /** sha256 hex digest of the canonical prompt payload. */\n readonly promptHash: string;\n /** Stable model identity string (for example `\"rev@release\"`). */\n readonly modelBinding: string;\n /** sha256 hex digest of the structured-output schema. */\n readonly schemaHash: string;\n /**\n * sha256 hex digest of the active policy profile. Required so two tenants\n * with identical prompts never share an in-flight slot.\n */\n readonly policyProfileHash: string;\n}\n\n/**\n * Schema-version literal pinned on every persisted\n * {@link GatewayIdempotencyKey} (Issue #1784, Story MA-3 #1758). Bumping\n * this constant requires a major contract bump and a CONTRACT_CHANGELOG.md\n * entry.\n */\nexport const GATEWAY_IDEMPOTENCY_KEY_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Inputs the LLM gateway hashes (HMAC-SHA256, operator-configured secret)\n * to derive the idempotency key for a single role-step attempt\n * (Issue #1784).\n *\n * The harness assembles these fields from already-known per-step values\n * — `jobId` and `roleStepId` from the execution graph (#1781), `attempt`\n * from the bounded harness state machine (#1780), `promptVersion` and\n * `schemaHash` from the agent role profile (#1779), and `inputHash`\n * from the canonical-JSON digest of the assembled prompt-and-schema\n * payload — so a worker that crashes mid-call can replay the same key\n * on resume and pull the cached result out of the gateway cache.\n */\nexport interface GatewayIdempotencyInputs {\n /** Job identifier this attempt belongs to. */\n readonly jobId: string;\n /** Stable role-step identifier (e.g. `\"<jobId>-generator-1\"`). */\n readonly roleStepId: string;\n /** Attempt index within the harness retry policy. */\n readonly attempt: number;\n /** Prompt-template version pin from the agent role profile. */\n readonly promptVersion: string;\n /** sha256 hex digest of the role's structured-output schema. */\n readonly schemaHash: string;\n /** sha256 hex digest of the canonical-JSON prompt + schema payload. */\n readonly inputHash: string;\n}\n\n/**\n * Persisted idempotency-key envelope written to\n * `<runDir>/agent/idempotency-cache/<hmac>.json` (Issue #1784). The\n * `hmac` field doubles as the content-addressable filename so a worker\n * resuming after a crash can compute the key from the inputs and\n * directly load the cached result.\n *\n * The `hmac` is the HMAC-SHA256 hex digest of\n * `canonicalJson({jobId, roleStepId, attempt, promptVersion, schemaHash, inputHash})`\n * keyed by the operator-configured gateway secret. The secret itself is\n * **never** persisted in any artifact, log, or error message.\n */\nexport interface GatewayIdempotencyKey {\n readonly schemaVersion: typeof GATEWAY_IDEMPOTENCY_KEY_SCHEMA_VERSION;\n readonly jobId: string;\n readonly roleStepId: string;\n readonly attempt: number;\n readonly promptVersion: string;\n readonly schemaHash: string;\n readonly inputHash: string;\n /** HMAC-SHA256 hex digest. Never persisted to artifacts in any other form. */\n readonly hmac: string;\n}\n\n/** Provider finish reasons normalized to a single set. */\nexport type LlmFinishReason =\n | \"stop\"\n | \"length\"\n | \"content_filter\"\n | \"tool_calls\"\n | \"other\";\n\n/** Success outcome — never includes reasoning/CoT traces. */\nexport interface LlmGenerationSuccess {\n outcome: \"success\";\n content: unknown;\n rawTextContent?: string;\n constrainedDecoding?: LlmConstrainedDecodingMetadata;\n finishReason: LlmFinishReason;\n usage: { inputTokens?: number; outputTokens?: number };\n modelDeployment: string;\n modelRevision: string;\n gatewayRelease: string;\n attempt: number;\n}\n\n/** Failure outcome with a redacted message and an explicit retryable flag. */\nexport interface LlmGenerationFailure {\n outcome: \"error\";\n errorClass: LlmGatewayErrorClass;\n message: string;\n constrainedDecoding?: LlmConstrainedDecodingMetadata;\n retryable: boolean;\n attempt: number;\n}\n\n/** Discriminated union returned by `LlmGatewayClient.generate`. */\nexport type LlmGenerationResult = LlmGenerationSuccess | LlmGenerationFailure;\n\n/** Theme brand policy applied during IR token derivation. */\nexport type WorkspaceBrandTheme = \"derived\" | \"sparkasse\";\n\n/** Router mode for generated React application shells. */\nexport type WorkspaceRouterMode = \"browser\" | \"hash\";\n\n/** Supported visual quality reference sources. */\nexport type WorkspaceVisualQualityReferenceMode =\n | \"figma_api\"\n | \"frozen_fixture\";\n\n/** Supported browser engines for visual quality capture. */\nexport type WorkspaceVisualBrowserName = \"chromium\" | \"firefox\" | \"webkit\";\n\n/** Optional overrides for the combined visual/performance quality weights. */\nexport interface WorkspaceCompositeQualityWeightsInput {\n visual?: number;\n performance?: number;\n}\n\n/** Output format for operational runtime logs. */\nexport type WorkspaceLogFormat = \"text\" | \"json\";\n\n/** Runtime status values for asynchronous workspace jobs. */\nexport type WorkspaceJobRuntimeStatus =\n | \"queued\"\n | \"running\"\n | \"partial\"\n | \"completed\"\n | \"failed\"\n | \"canceled\";\n\n/** Structured stage names exposed by Test Intelligence jobs. */\nexport type WorkspaceJobStageName =\n | \"figma.source\"\n | \"ir.derive\"\n | \"template.prepare\"\n | \"codegen.generate\"\n | \"validate.project\"\n | \"repro.export\"\n | \"git.pr\";\n\n/** Structured request-time pipeline selection failures. */\nexport const ALLOWED_PIPELINE_REQUEST_ERROR_CODES = [\n \"INVALID_PIPELINE\",\n \"PIPELINE_UNAVAILABLE\",\n \"PIPELINE_INPUT_UNSUPPORTED\",\n \"PIPELINE_SOURCE_MODE_UNSUPPORTED\",\n \"PIPELINE_SCOPE_UNSUPPORTED\",\n] as const;\n\n/** Configuration for starting a Test Intelligence server instance. */\nexport interface WorkspaceStartOptions {\n /** Host to bind to. Default: \"127.0.0.1\" */\n host?: string;\n /** Port to bind to. Default: 1983 */\n port?: number;\n /** Project-specific working directory. Default: process.cwd() */\n workDir?: string;\n /** Output root relative to workDir or as absolute path. Default: \".test-intelligence\" */\n outputRoot?: string;\n /** Startup cleanup TTL for stale tmp-figma-paste JSON files in milliseconds. Default: 86400000 */\n figmaPasteTempTtlMs?: number;\n /** Figma request timeout in milliseconds. Default: 30000 */\n figmaRequestTimeoutMs?: number;\n /** Figma retry attempts. Default: 3 */\n figmaMaxRetries?: number;\n /** Consecutive transient failures before the Figma REST circuit breaker opens. Default: 3 */\n figmaCircuitBreakerFailureThreshold?: number;\n /** Duration in milliseconds that the Figma REST circuit breaker stays open before a probe request is allowed. Default: 30000 */\n figmaCircuitBreakerResetTimeoutMs?: number;\n /** Bootstrap depth for large-board staged fetch. Default: 5 */\n figmaBootstrapDepth?: number;\n /** Candidate node batch size for staged fetch. Default: 6 */\n figmaNodeBatchSize?: number;\n /** Number of concurrent staged /nodes fetch workers. Default: 3 */\n figmaNodeFetchConcurrency?: number;\n /** Enable adaptive node batch splitting on repeated oversized responses. Default: true */\n figmaAdaptiveBatchingEnabled?: boolean;\n /** Maximum staged screen candidates to fetch. Default: 40 */\n figmaMaxScreenCandidates?: number;\n /** Optional case-insensitive regex used to include staged screen candidates by name. */\n figmaScreenNamePattern?: string;\n /** Enable file-system cache for figma.source fetches. Default: true */\n figmaCacheEnabled?: boolean;\n /** Cache TTL for figma.source entries in milliseconds. Default: 900000 */\n figmaCacheTtlMs?: number;\n /** Maximum Figma JSON response bytes accepted before parse fallback/failure. Default: 67108864 */\n maxJsonResponseBytes?: number;\n /** Maximum IR cache entry count before eviction. Default: 50 */\n maxIrCacheEntries?: number;\n /** Maximum IR cache bytes retained on disk before eviction. Default: 134217728 */\n maxIrCacheBytes?: number;\n /** Path to icon fallback mapping file (JSON). Default: <outputRoot>/icon-fallback-map.json */\n iconMapFilePath?: string;\n /** Path to design-system mapping file (JSON). Default: <outputRoot>/design-system.json */\n designSystemFilePath?: string;\n /** Enable Figma image asset export to generated-app/public/images. Default: true */\n exportImages?: boolean;\n /** Maximum IR elements per screen before deterministic truncation. Default: 1200 */\n figmaScreenElementBudget?: number;\n /** Configured baseline depth limit for dynamic IR child traversal. Default: 14 */\n figmaScreenElementMaxDepth?: number;\n /** Token brand policy used when deriving IR tokens. Default: \"derived\" */\n brandTheme?: WorkspaceBrandTheme;\n /** Optional Sparkasse design-token file used only when `brandTheme=\"sparkasse\"`; when omitted, built-in defaults are used. */\n sparkasseTokensFilePath?: string;\n /** Locale used for deterministic select-option number derivation. Default: \"de-DE\" */\n generationLocale?: string;\n /** Router mode for generated App.tsx shell. Default: \"browser\" */\n routerMode?: WorkspaceRouterMode;\n /** Timeout for external commands (pnpm/git) in milliseconds. Default: 900000 */\n commandTimeoutMs?: number;\n /** Maximum retained stdout bytes per external command before truncation/spooling. Default: 1048576 */\n commandStdoutMaxBytes?: number;\n /** Maximum retained stderr bytes per external command before truncation/spooling. Default: 1048576 */\n commandStderrMaxBytes?: number;\n /** Maximum structured diagnostics retained per pipeline error. Default: 25 */\n pipelineDiagnosticMaxCount?: number;\n /** Maximum message/suggestion characters retained per structured diagnostic. Default: 320 */\n pipelineDiagnosticTextMaxLength?: number;\n /** Maximum object keys retained per structured diagnostic details object. Default: 30 */\n pipelineDiagnosticDetailsMaxKeys?: number;\n /** Maximum array items retained per structured diagnostic details array. Default: 20 */\n pipelineDiagnosticDetailsMaxItems?: number;\n /** Maximum nesting depth retained when sanitizing structured diagnostic details. Default: 4 */\n pipelineDiagnosticDetailsMaxDepth?: number;\n /** Maximum validation retry attempts for lint/typecheck/build correction loops. Default: 3 */\n maxValidationAttempts?: number;\n /** Run lint auto-fix during validate.project before lint/typecheck/build. Default: true */\n enableLintAutofix?: boolean;\n /** Run perf validation during validate.project. Default: false */\n enablePerfValidation?: boolean;\n /** Run generated-project UI validation in validate.project, including static checks and optional browser visual matrix when the template provides `validate:playwright`. Default: false */\n enableUiValidation?: boolean;\n /** Run visual quality validation in validate.project. Default: false */\n enableVisualQualityValidation?: boolean;\n /** Reference source for visual quality validation. Default: \"figma_api\" when enabled */\n visualQualityReferenceMode?: WorkspaceVisualQualityReferenceMode;\n /** Viewport width used when capturing generated output for visual quality validation. Default: 1280 */\n visualQualityViewportWidth?: number;\n /** Viewport height used when capturing generated output for visual quality validation. Default: 800 */\n visualQualityViewportHeight?: number;\n /** Device pixel ratio used when capturing generated output for visual quality validation. Default: 1 */\n visualQualityDeviceScaleFactor?: number;\n /** Browser engines used when capturing generated output for visual quality validation. Default: [\"chromium\"] */\n visualQualityBrowsers?: WorkspaceVisualBrowserName[];\n /** Weight overrides used when computing the combined visual/performance quality score. Default: visual 0.6, performance 0.4 */\n compositeQualityWeights?: WorkspaceCompositeQualityWeightsInput;\n /** Run generated-project unit tests in validate.project. Default: false */\n enableUnitTestValidation?: boolean;\n /** Make generated-project unit test failures non-fatal. When true, test results are recorded but failures do not throw. Default: false */\n unitTestIgnoreFailure?: boolean;\n /** Prefer offline package resolution during generated-project install. Default: true */\n installPreferOffline?: boolean;\n /** Skip package installation in validate.project; requires existing node_modules. Default: false */\n skipInstall?: boolean;\n /** Maximum number of jobs that may run concurrently. Default: 1 */\n maxConcurrentJobs?: number;\n /** Maximum number of queued jobs waiting for execution before backpressure rejects submit. Default: 20 */\n maxQueuedJobs?: number;\n /** Maximum retained job log entries. Default: 300 */\n logLimit?: number;\n /** Maximum on-disk bytes for job-owned roots before the pipeline fails. Default: 536870912 */\n maxJobDiskBytes?: number;\n /** Output format for operational runtime logs. Default: \"text\" */\n logFormat?: WorkspaceLogFormat;\n /** Maximum accepted job submissions and import-session event writes per minute for a single client IP, enforced separately per route family. Use 0 to disable. Default: 10 */\n rateLimitPerMinute?: number;\n /** Maximum graceful shutdown drain time in milliseconds before remaining connections are terminated. Default: 10000 */\n shutdownTimeoutMs?: number;\n /**\n * Bearer token accepted for `POST /workspace/import-sessions/:id/events`.\n * When omitted, import-session event writes fail closed.\n */\n importSessionEventBearerToken?: string;\n /** Enable local preview export and serving. Default: true */\n enablePreview?: boolean;\n /** Optional custom fetch implementation (for tests or custom runtimes). */\n fetchImpl?: typeof fetch;\n /**\n * @deprecated Reserved for backward compatibility with callers that reuse\n * submit-time option objects. Isolated child startup ignores this field and\n * it does not define any server-start target-root behavior.\n */\n targetPath?: string;\n /**\n * Opt-in startup feature gate for test-case generation.\n *\n * Test Intelligence is local-first by design. The feature is reachable only\n * when both this startup option and the `TEST_INTELLIGENCE_ENABLED=1`\n * environment variable are enabled; otherwise, submitting a\n * `figma_to_qc_test_cases` job fails closed with a `503 Feature Disabled`\n * response and performs no side effects.\n */\n testIntelligence?: {\n /** Whether test-intelligence features may be invoked at runtime. Default: false. */\n enabled: boolean;\n /**\n * Whether the Wave 4 multi-source ingestion gate (Issue #1431) is\n * permitted at runtime. Default: false. Strictly nested inside\n * {@link enabled}: even when this flag is true, multi-source\n * ingestion still fails closed unless `enabled === true` _and_ the\n * `TEST_INTELLIGENCE_ENABLED` /\n * `TEST_INTELLIGENCE_MULTISOURCE_ENABLED` environment gates are both\n * set. Operators may flip this off at startup to\n * halt multi-source ingestion without redeploying.\n */\n multiSourceEnabled?: boolean;\n /**\n * Bearer token accepted by the Inspector test-intelligence review-gate\n * write routes (`POST /workspace/test-intelligence/review/...`). When\n * omitted or blank, review writes fail closed with `503` until the\n * operator configures a token. Reads do not require this token. This\n * legacy token is treated as one authenticated principal; configure\n * `reviewPrincipals` for true two-distinct-principal four-eyes approval.\n */\n reviewBearerToken?: string;\n /**\n * Principal-bound review credentials. When configured, approval actor\n * identity is derived from the matching bearer token rather than from\n * the request body, preventing forged reviewer identities (#1376).\n */\n reviewPrincipals?: TestIntelligenceReviewPrincipal[];\n /**\n * Optional override for the directory under which per-job\n * test-intelligence artifacts are stored and read by the Inspector\n * UI. When omitted, defaults to `<outputRoot>/test-intelligence`.\n * The directory is treated as opaque storage; missing artifacts\n * surface as empty UI states rather than errors.\n */\n artifactRoot?: string;\n /**\n * Risk categories for which the review gate must enforce four-eyes\n * approval (#1376). When omitted, defaults to\n * `DEFAULT_FOUR_EYES_REQUIRED_RISK_CATEGORIES`. Values outside the\n * `TestCaseRiskCategory` taxonomy are ignored. An empty array\n * disables risk-driven enforcement (visual-sidecar triggers still\n * apply unless `fourEyesVisualSidecarTriggerOutcomes` is also\n * empty).\n */\n fourEyesRequiredRiskCategories?: TestCaseRiskCategory[];\n /**\n * Visual-sidecar validation outcomes that trigger four-eyes review\n * for any case whose Figma trace references a screen carrying the\n * outcome (#1376, 2026-04-24 multimodal addendum). Defaults to\n * `DEFAULT_FOUR_EYES_VISUAL_SIDECAR_TRIGGERS`.\n */\n fourEyesVisualSidecarTriggerOutcomes?: VisualSidecarValidationOutcome[];\n /**\n * Whether the controlled OpenText ALM API transfer pipeline (#1372)\n * is allowed at runtime. Defaults to `false` (fail-closed). Even\n * when `true`, every other gate (feature flag, bearer token, dry-run\n * report, four-eyes, policy) must still pass before any write\n * leaves the process. Operators may flip this off to halt transfer\n * without redeploying.\n */\n allowApiTransfer?: boolean;\n /**\n * Bearer token accepted by the controlled OpenText ALM API transfer\n * pipeline (#1372) when `allowApiTransfer=true`. When omitted or\n * blank, every transfer attempt fails closed with\n * `bearer_token_missing`. The token is matched against the\n * caller-supplied bearer using a SHA-256 timing-safe compare so\n * incorrect lengths do not leak via timing. The token is treated as\n * a single authenticated principal; configure `transferPrincipals`\n * for multi-principal idempotent transfer audit trails.\n */\n transferBearerToken?: string;\n /**\n * Principal-bound transfer credentials (#1372). When configured,\n * the principal id of the matching token is recorded in\n * `transfer-report.json` audit metadata, enabling per-operator\n * audit lineage on top of the bearer-token check.\n */\n transferPrincipals?: TestIntelligenceTransferPrincipal[];\n /**\n * Whether the Jira sub-task write pipeline (#1482) is allowed at\n * runtime. Defaults to `false` (fail-closed). Even when `true`,\n * every other gate (feature flag, bearer token, parent issue key,\n * approved cases, policy/visual sidecar clear) must still pass\n * before any write leaves the process. Operators may flip this off\n * to halt Jira writes without redeploying.\n */\n allowJiraWrite?: boolean;\n /**\n * Bearer token used by the Jira sub-task write pipeline (#1482).\n * Fail-closed when omitted: every Jira write attempt refuses with\n * `bearer_token_missing`. The token is supplied to the configured\n * `JiraWriteClient` and is never persisted into emitted artifacts.\n */\n jiraWriteBearerToken?: string;\n };\n}\n\n/** Scoring weights for the visual quality composite score. */\nexport interface WorkspaceVisualScoringWeights {\n layoutAccuracy: number;\n colorFidelity: number;\n typography: number;\n componentStructure: number;\n spacingAlignment: number;\n}\n\n/** Per-dimension score in a visual quality report. */\nexport interface WorkspaceVisualDimensionScore {\n name: string;\n weight: number;\n score: number;\n details: string;\n}\n\n/** Deviation hotspot identified in a visual quality comparison. */\nexport interface WorkspaceVisualDeviationHotspot {\n rank: number;\n region: string;\n x: number;\n y: number;\n width: number;\n height: number;\n deviationPercent: number;\n severity: \"low\" | \"medium\" | \"high\" | \"critical\";\n category: \"layout\" | \"color\" | \"typography\" | \"component\" | \"spacing\";\n}\n\n/** Metadata about a visual quality comparison run. */\nexport interface WorkspaceVisualComparisonMetadata {\n comparedAt: string;\n imageWidth: number;\n imageHeight: number;\n totalPixels: number;\n diffPixelCount: number;\n configuredWeights: WorkspaceVisualScoringWeights;\n viewport: {\n width: number;\n height: number;\n deviceScaleFactor: number;\n };\n versions: {\n packageVersion: string;\n contractVersion: string;\n };\n}\n\nexport interface WorkspaceVisualCrossBrowserPairwiseDiff {\n browserA: WorkspaceVisualBrowserName;\n browserB: WorkspaceVisualBrowserName;\n diffPercent: number;\n diffImagePath?: string;\n}\n\nexport interface WorkspaceVisualCrossBrowserConsistency {\n browsers: WorkspaceVisualBrowserName[];\n consistencyScore: number;\n pairwiseDiffs: WorkspaceVisualCrossBrowserPairwiseDiff[];\n warnings?: string[];\n}\n\nexport interface WorkspaceVisualPerBrowserResult {\n browser: WorkspaceVisualBrowserName;\n overallScore: number;\n actualImagePath?: string;\n diffImagePath?: string;\n reportPath?: string;\n warnings?: string[];\n}\n\nexport interface WorkspaceVisualComponentCoverage {\n comparedCount: number;\n skippedCount: number;\n coveragePercent: number;\n bySkipReason: Record<string, number>;\n}\n\nexport interface WorkspaceVisualQualityComponentEntry {\n componentId: string;\n componentName: string;\n status: \"compared\" | \"skipped\";\n score?: number;\n diffImagePath?: string;\n reportPath?: string;\n skipReason?: string;\n storyEntryId?: string;\n referenceNodeId?: string;\n warnings?: string[];\n}\n\n/** Full visual quality report produced by the scoring system. */\nexport interface WorkspaceVisualQualityReport {\n status: \"completed\" | \"failed\" | \"not_requested\";\n referenceSource?: WorkspaceVisualQualityReferenceMode;\n capturedAt?: string;\n overallScore?: number;\n interpretation?: string;\n dimensions?: WorkspaceVisualDimensionScore[];\n componentAggregateScore?: number;\n componentCoverage?: WorkspaceVisualComponentCoverage;\n components?: WorkspaceVisualQualityComponentEntry[];\n diffImagePath?: string;\n hotspots?: WorkspaceVisualDeviationHotspot[];\n metadata?: WorkspaceVisualComparisonMetadata;\n browserBreakdown?: Partial<Record<WorkspaceVisualBrowserName, number>>;\n crossBrowserConsistency?: WorkspaceVisualCrossBrowserConsistency;\n perBrowser?: WorkspaceVisualPerBrowserResult[];\n warnings?: string[];\n message?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Remap suggestion types for guided stale-draft override remapping (#466)\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Generation confidence model types (#849)\n// ---------------------------------------------------------------------------\n\n/**\n * Business Test Intent IR surface (Issue #1361).\n *\n * The IR is the sanitized, test-design-oriented input that the downstream\n * test-case generator consumes. Raw Figma payloads must never reach prompt\n * compilation — PII-like mock values are detected and replaced with opaque\n * redaction tokens before any artifact is persisted.\n */\n\n/** Schema version for `BusinessTestIntentIr` artifacts. */\nexport const BUSINESS_TEST_INTENT_IR_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Schema version for persisted `TestDesignModel` projection artifacts. */\nexport const TEST_DESIGN_MODEL_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for persisted `TestDesignModel` artifacts. */\nexport const TEST_DESIGN_MODEL_ARTIFACT_FILENAME =\n \"test-design-model.json\" as const;\n\n/** Schema version for persisted agent-role prompt-run artifacts. */\nexport const AGENT_ROLE_RUN_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Directory containing per-role prompt-run metadata artifacts. */\nexport const AGENT_ROLE_RUN_ARTIFACT_DIRECTORY = \"agent-role-runs\" as const;\n\n/**\n * Schema version for the static `AgentRoleProfile` matrix introduced for\n * Issue #1779 (Story MA-3). The profile shape is locked behind this\n * version; structural changes require a major bump and a migration entry\n * in `CONTRACT_CHANGELOG.md`.\n */\nexport const AGENT_ROLE_PROFILE_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Closed runtime list of agent harness roles tracked by the Production\n * Runner state machine. The order is alphabetical for stable\n * canonical-JSON serialisation; `contract-version.test.ts` enforces the\n * shape via the public-export snapshot.\n */\nexport const AGENT_HARNESS_ROLES = [\n \"action_topology\",\n \"adversarial_critic\",\n \"adversarial_gap_finder\",\n \"final_verifier\",\n \"generator\",\n \"human_review\",\n \"logic_judge\",\n \"repair_planner\",\n \"semantic_judge\",\n \"visual_sidecar\",\n] as const;\n\n/** Closed runtime list of agent-role capability filters. */\nexport const AGENT_ROLE_CAPABILITIES = [\n \"none\",\n \"propose_changes\",\n \"read_artifacts\",\n \"score_only\",\n] as const;\n\n/** Closed runtime list of agent-role kinds. */\nexport const AGENT_ROLE_KINDS = [\"deterministic_service\", \"llm_role\"] as const;\n\n/** Closed runtime list of FinOps attribution groups for agent-role runs. */\nexport const AGENT_ROLE_FINOPS_GROUPS = [\n \"adversarial\",\n \"generation\",\n \"judge\",\n \"repair\",\n \"verification\",\n \"visual\",\n] as const;\n\n/** Closed runtime list of allowed `maxAttempts` budgets for an agent role. */\nexport const AGENT_ROLE_MAX_ATTEMPT_VALUES = [1, 2, 3] as const;\n\n/**\n * Discrete role identifiers consumed by the multi-agent harness state\n * machine.\n *\n * - `visual_sidecar` — deterministic screenshot/spec-check service.\n * - `generator` — LLM that produces structured test cases.\n * - `logic_judge` — LLM that validates generator output against the\n * deterministic design and coverage artifacts.\n * - `semantic_judge` — LLM judge panel that scores generated cases.\n * - `adversarial_gap_finder` — LLM that surfaces missing coverage.\n * - `repair_planner` — LLM that proposes a structured repair plan\n * consumed by a deterministic apply-step gated by `RepairChangeGuard`.\n * - `final_verifier` — deterministic post-repair verifier that produces\n * the evidence anchor.\n */\nexport type AgentHarnessRole = (typeof AGENT_HARNESS_ROLES)[number];\n\n/**\n * Capability filter that determines what side effects a role is allowed\n * to declare. The boundary lint in\n * `src/test-intelligence/agent-role-profile.test.ts` proves that no role\n * with `roleKind === \"llm_role\"` is ever assigned `propose_changes`;\n * filesystem / gateway / review-store mutations are reserved for\n * deterministic services.\n */\nexport type AgentRoleCapability = (typeof AGENT_ROLE_CAPABILITIES)[number];\n\n/** Whether a role is a deterministic service or a LLM-driven role. */\nexport type AgentRoleKind = (typeof AGENT_ROLE_KINDS)[number];\n\n/** FinOps attribution group used for per-source cost rollups. */\nexport type AgentRoleFinOpsGroup = (typeof AGENT_ROLE_FINOPS_GROUPS)[number];\n\n/**\n * Closed runtime list of model-family markers consumed by the\n * cross-family judge ensemble (Issue #2038). The vocabulary is\n * deliberately small: only families with judge-grade models are\n * surfaced. Extending the list requires a contract bump.\n */\nexport const JUDGE_MODEL_FAMILIES = [\n \"anthropic\",\n \"azure-openai\",\n \"google\",\n \"in-house\",\n \"mistral\",\n \"openai\",\n] as const;\n\n/** Discriminated alias for {@link JUDGE_MODEL_FAMILIES}. */\nexport type JudgeModelFamily = (typeof JUDGE_MODEL_FAMILIES)[number];\n\n/**\n * Closed runtime list of deployment-region markers consumed by the\n * EU-residency policy gate (Issue #2038). `eu` is the only region\n * accepted under `eu-banking-default`; `us` and `global` are accepted\n * for non-EU profiles.\n */\nexport const JUDGE_MODEL_REGIONS = [\"eu\", \"global\", \"us\"] as const;\n\n/** Discriminated alias for {@link JUDGE_MODEL_REGIONS}. */\nexport type JudgeModelRegion = (typeof JUDGE_MODEL_REGIONS)[number];\n\n/**\n * Static binding of an agent role to a model identity. The optional\n * `ictRegisterRef` is mandatory under `policyProfile = \"banking\"` and is\n * enforced by Wave MA-4; this contract defines the slot but does not\n * itself enforce the banking policy.\n */\nexport interface AgentModelBinding {\n /** Stable provider identifier (e.g., `\"azure-openai\"`, `\"in-house\"`). */\n readonly providerId: string;\n /** Stable model identifier inside the provider namespace. */\n readonly modelId: string;\n /**\n * Optional inference profile / deployment identifier for providers\n * that route by deployment name (e.g., Azure deployments).\n */\n readonly inferenceProfileId?: string;\n /**\n * Optional reference into the operator's ICT register. Mandatory\n * under banking profiles (enforced in MA-4) so deployed models map\n * back to a registered ICT asset.\n */\n readonly ictRegisterRef?: string;\n /**\n * Optional model-family marker (Issue #2038). Used by the\n * cross-family judge ensemble to enforce that no two judge roles in\n * the same run draw from the same model family. Must be one of\n * {@link JUDGE_MODEL_FAMILIES} when present.\n */\n readonly family?: JudgeModelFamily;\n /**\n * Optional deployment-region marker (Issue #2038). Used by EU\n * data-residency policies (e.g., `eu-banking-default`) to refuse\n * non-EU endpoints during judge-binding validation. Must be one of\n * {@link JUDGE_MODEL_REGIONS} when present.\n */\n readonly region?: JudgeModelRegion;\n}\n\n/**\n * Stable tier labels used by the production multi-model routing policy\n * (Issue #2099). The vocabulary is intentionally small and auditable:\n * - `light` — cheap/fast text roles used for triage or low-risk helpers.\n * - `heavy` — flagship reasoning roles.\n * - `multimodal` — screenshot/document-aware roles.\n */\nexport const MODEL_ROUTING_TIER_LABELS = [\n \"light\",\n \"heavy\",\n \"multimodal\",\n] as const;\n\n/** Discriminated alias for {@link MODEL_ROUTING_TIER_LABELS}. */\nexport type ModelRoutingTierLabel = (typeof MODEL_ROUTING_TIER_LABELS)[number];\n\n/**\n * Routing slots within one role. Most active runtime roles expose one slot,\n * but the contract permits explicit fallback / triage / diversity bindings\n * without adding new top-level role ids.\n */\nexport const MODEL_ROUTING_ROUTE_SLOTS = [\n \"primary\",\n \"secondary\",\n \"fallback\",\n \"triage\",\n] as const;\n\n/** Discriminated alias for {@link MODEL_ROUTING_ROUTE_SLOTS}. */\nexport type ModelRoutingRouteSlot = (typeof MODEL_ROUTING_ROUTE_SLOTS)[number];\n\n/**\n * Closed runtime list of roles that may participate in the typed\n * multi-model routing policy. The list includes the production-runner roles\n * in use today plus additive foundation-only roles from Issue #2099 whose\n * runtime wiring may land in follow-up stories.\n */\nexport const MODEL_ROUTING_ROLES = [\n \"test_generation\",\n \"logic_judge\",\n \"coverage_planner\",\n \"risk_ranker\",\n \"visual_primary\",\n \"visual_fallback\",\n \"a11y_judge\",\n \"faithfulness_judge\",\n \"document_ingestion\",\n \"adversarial_critic\",\n \"calibration_holdout_generator\",\n] as const;\n\n/** Discriminated alias for {@link MODEL_ROUTING_ROLES}. */\nexport type ModelRoutingRole = (typeof MODEL_ROUTING_ROLES)[number];\n\n/** Schema version pinned on every persisted {@link ModelRoutingPolicy}. */\nexport const MODEL_ROUTING_POLICY_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * One resolved route in the typed multi-model policy. The route carries the\n * static model binding plus the concrete deployment/revision identity the\n * current runtime selected so replay keys, FinOps, and evidence artifacts can\n * attest the exact model path without re-reading environment variables.\n */\nexport interface ModelRoutingRoute {\n /** Stable role identifier this route binds. */\n readonly role: ModelRoutingRole;\n /** Slot within the role (`primary`, `fallback`, `triage`, ...). */\n readonly slot: ModelRoutingRouteSlot;\n /** Cost/capability tier label for this route. */\n readonly tierLabel: ModelRoutingTierLabel;\n /** Stable provider/model/deployment binding. */\n readonly modelBinding: AgentModelBinding;\n /** Concrete model revision selected for this route, when known. */\n readonly modelRevision?: string;\n /** Concrete gateway release selected for this route, when known. */\n readonly gatewayRelease?: string;\n}\n\n/**\n * Typed, canonical model-routing policy for one job/profile. The policy is an\n * auditable input into client construction and replay identity rather than a\n * dynamic rule engine.\n */\nexport interface ModelRoutingPolicy {\n readonly schemaVersion: typeof MODEL_ROUTING_POLICY_SCHEMA_VERSION;\n /** Stable policy identifier, e.g. `eu-banking-default`. */\n readonly policyId: string;\n /** Stable version stamp of the policy definition. */\n readonly policyVersion: string;\n /** Policy profile that selected this routing policy. */\n readonly policyProfileId: string;\n /** Sorted, canonical list of active routes. */\n readonly routes: readonly ModelRoutingRoute[];\n}\n\n/** Additive per-job override entry layered over a base routing policy. */\nexport interface ModelRoutingOverrideRoute {\n readonly role: ModelRoutingRole;\n readonly slot?: ModelRoutingRouteSlot;\n readonly tierLabel?: ModelRoutingTierLabel;\n readonly modelBinding?: AgentModelBinding;\n readonly modelRevision?: string;\n readonly gatewayRelease?: string;\n}\n\n/**\n * Optional per-job routing override supplied by operators. The override is\n * additive and explicit: callers override individual routes rather than\n * replacing the whole policy object.\n */\nexport interface ModelRoutingOverride {\n readonly routes: readonly ModelRoutingOverrideRoute[];\n}\n\n/**\n * Static, hand-rolled profile that pins a role to a budget tier,\n * capability filter, output schema, and FinOps group. Profiles are\n * frozen at module load and serialise to canonical JSON for evidence\n * anchoring.\n */\nexport interface AgentRoleProfile {\n /** Pinned schema version literal. Bumping requires a major contract bump. */\n readonly schemaVersion: typeof AGENT_ROLE_PROFILE_SCHEMA_VERSION;\n /** Role identifier this profile binds to. */\n readonly role: AgentHarnessRole;\n /** Whether the role is deterministic or LLM-driven. */\n readonly roleKind: AgentRoleKind;\n /**\n * Optional prompt-template version pin. Required for `llm_role`\n * profiles (validated at registry construction); omitted for\n * deterministic services that do not compile a prompt.\n */\n readonly promptVersion?: string;\n /**\n * Optional model binding. Required for `llm_role` profiles\n * (validated at registry construction); omitted for deterministic\n * services that do not call the gateway.\n */\n readonly modelBinding?: AgentModelBinding;\n /** Stable identifier of the structured-output JSON schema this role emits. */\n readonly outputSchema: string;\n /** Maximum attempts the harness may make before declaring failure. */\n readonly maxAttempts: 1 | 2 | 3;\n /** Hard cap on input tokens passed to the gateway for this role. */\n readonly maxInputTokens: number;\n /** Hard cap on output tokens the gateway may emit for this role. */\n readonly maxOutputTokens: number;\n /** Capability filter — what kinds of side effects the role may declare. */\n readonly capability: AgentRoleCapability;\n /** FinOps attribution group used for cost rollups. */\n readonly finOpsGroup: AgentRoleFinOpsGroup;\n}\n\n/**\n * Schema version literal pinned on every persisted\n * {@link AgentHarnessExecutionGraph} artifact (Issue #1781). Structural\n * changes require a major bump and a `CONTRACT_CHANGELOG.md` entry.\n */\nexport const AGENT_HARNESS_EXECUTION_GRAPH_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Closed runtime list of retry policies a node in the execution graph\n * may declare. The harness state machine (#1780) consumes the literal\n * to decide whether a transient gateway error is retried in-place,\n * resumed from the most recent checkpoint, or terminal.\n */\nexport const AGENT_HARNESS_GRAPH_RETRY_POLICIES = [\n \"none\",\n \"retry_from_checkpoint\",\n \"retry_transient_once\",\n] as const;\n\n/** Retry policy for a single node in {@link AgentHarnessExecutionGraph}. */\nexport type AgentHarnessGraphRetryPolicy =\n (typeof AGENT_HARNESS_GRAPH_RETRY_POLICIES)[number];\n\n/**\n * One node in the multi-agent harness execution DAG. The shape is\n * intentionally minimal — `blocks` and `blockedBy` form a reversible\n * adjacency record that callers can canonicalise byte-for-byte. The\n * harness Production Runner reads `requiredInputArtifacts` and\n * `producedArtifacts` to wire role-step IO without a workflow engine.\n *\n * The graph is not a workflow framework: there is no scheduler, no\n * trigger, no conditional. Edge ordering is alphabetical on\n * `roleStepId`.\n */\nexport interface AgentHarnessGraphNode {\n /** Stable per-step identifier (e.g., `\"<jobId>-generator-1\"`). */\n readonly roleStepId: string;\n /** Role this step is bound to. */\n readonly role: AgentHarnessRole;\n /**\n * Downstream `roleStepId`s this node unblocks once its outcome is\n * `accepted` or `needs_review`. Sorted alphabetically; mirrored by\n * each downstream node's `blockedBy`.\n */\n readonly blocks: readonly string[];\n /**\n * Upstream `roleStepId`s that must reach a non-failed terminal\n * outcome before this node may run. Sorted alphabetically;\n * mirrored by each upstream node's `blocks`.\n */\n readonly blockedBy: readonly string[];\n /**\n * Stable artifact identifiers (filenames or content addresses) the\n * step requires as input. Sorted alphabetically.\n */\n readonly requiredInputArtifacts: readonly string[];\n /**\n * Stable artifact identifiers the step is expected to produce.\n * Sorted alphabetically.\n */\n readonly producedArtifacts: readonly string[];\n /** Retry policy applied to this node. */\n readonly retryPolicy: AgentHarnessGraphRetryPolicy;\n}\n\n/**\n * Persisted execution-graph artifact for a single Production Runner\n * job. The harness consumes the graph to drive role-step ordering and\n * to skip already-completed steps on resume. Canonical-JSON-stable for\n * byte-identical inputs; the `graphHash` is the sha256 of the\n * canonical-JSON representation of the `nodes` array.\n */\nexport interface AgentHarnessExecutionGraph {\n /** Pinned schema version literal. */\n readonly schemaVersion: typeof AGENT_HARNESS_EXECUTION_GRAPH_SCHEMA_VERSION;\n /** Job identifier this graph belongs to. */\n readonly jobId: string;\n /** sha256 hex digest of `canonicalJson(nodes)`. 64 lowercase hex chars. */\n readonly graphHash: string;\n /** Nodes sorted alphabetically by `roleStepId`. */\n readonly nodes: readonly AgentHarnessGraphNode[];\n}\n\n/**\n * Closed runtime list of terminal outcomes the team-artifact roll-up\n * may report. Mirrors the harness step-level vocabulary so the team\n * artifact does not introduce a second outcome surface.\n */\nexport const ALLOWED_AGENT_TEAM_OUTCOMES = [\n \"accepted\",\n \"blocked\",\n \"failed_permanent\",\n \"failed_retryable\",\n \"needs_review\",\n] as const;\n\n/** Terminal outcome of an entire agent-team run. */\nexport type AgentTeamOutcome = (typeof ALLOWED_AGENT_TEAM_OUTCOMES)[number];\n\n/** Schema version literal pinned on `agent-team-config.json`. */\nexport const AGENT_TEAM_CONFIG_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the per-run team configuration artifact. */\nexport const AGENT_TEAM_CONFIG_ARTIFACT_FILENAME =\n \"agent-team-config.json\" as const;\n\n/** Schema version literal pinned on `agent-team-results.json`. */\nexport const AGENT_TEAM_RESULTS_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the per-run team results artifact. */\nexport const AGENT_TEAM_RESULTS_ARTIFACT_FILENAME =\n \"agent-team-results.json\" as const;\n\n/**\n * Persisted team-configuration artifact written once per job at run\n * start. Contains only profile metadata, the graph hash, and the\n * policy-profile hash — no secrets, no raw prompts, no chain-of-thought.\n */\nexport interface AgentTeamConfigArtifact {\n /** Pinned schema version literal. */\n readonly schemaVersion: typeof AGENT_TEAM_CONFIG_SCHEMA_VERSION;\n /** Job identifier this configuration belongs to. */\n readonly jobId: string;\n /** Profiles wired into this run, sorted alphabetically by role. */\n readonly profiles: readonly AgentRoleProfile[];\n /** sha256 hex digest of the run's {@link AgentHarnessExecutionGraph}. */\n readonly graphHash: string;\n /**\n * sha256 hex digest of the canonical-JSON of the active policy\n * profile (e.g., banking, neutral). Used to scope idempotency and\n * gateway in-flight dedup keys.\n */\n readonly policyProfileHash: string;\n /** Hard guarantee that the artifact never carries a raw prompt. */\n readonly rawPromptsIncluded: false;\n}\n\n/** Per-role-step rolled-up record persisted in the team-results artifact. */\nexport interface AgentTeamRoleRunSummary {\n /** Step identifier (matches the per-step harness rollup filename). */\n readonly roleStepId: string;\n /** Role that produced the step. */\n readonly role: AgentHarnessRole;\n /** Terminal outcome from the harness state machine. */\n readonly outcome: AgentTeamOutcome;\n /** Closed taxonomy error class recorded by the harness. */\n readonly errorClass: string;\n /** Job-runtime status onto which the outcome was mapped. */\n readonly mappedJobStatus: \"completed\" | \"failed\" | \"partial\";\n /** Number of attempts the harness consumed for this step. */\n readonly attemptsConsumed: number;\n /** sha256 hex digest of the canonical-JSON step rollup artifact. */\n readonly artifactHash: string;\n /** Cost rollup across attempts; never includes pricing data. */\n readonly costsRollup: {\n readonly inputTokens: number;\n readonly outputTokens: number;\n readonly totalLatencyMs: number;\n };\n}\n\n/** Cost rollup across every role-run in the team. */\nexport interface AgentTeamTotalCost {\n readonly inputTokens: number;\n readonly outputTokens: number;\n readonly totalLatencyMs: number;\n}\n\n/**\n * Persisted team-results artifact written once per job at run end.\n * Contains only hashes, status fields, and aggregate cost — never\n * secrets, raw prompts, raw screenshots, or chain-of-thought.\n */\nexport interface AgentTeamResultsArtifact {\n /** Pinned schema version literal. */\n readonly schemaVersion: typeof AGENT_TEAM_RESULTS_SCHEMA_VERSION;\n /** Job identifier this results bundle belongs to. */\n readonly jobId: string;\n /** sha256 hex digest of the graph used by this run. */\n readonly graphHash: string;\n /** Aggregate outcome across the team. */\n readonly outcome: AgentTeamOutcome;\n /** Per-role-step summaries sorted alphabetically by `roleStepId`. */\n readonly roleRuns: readonly AgentTeamRoleRunSummary[];\n /** Aggregate cost rollup. */\n readonly totalCost: AgentTeamTotalCost;\n /** Hard guarantee that the artifact never carries a raw prompt. */\n readonly rawPromptsIncluded: false;\n}\n\n// ---------------------------------------------------------------------------\n// Issue #1782 — Agent_02 Judge Panel (PoLL) verdict artifact.\n// ---------------------------------------------------------------------------\n\n/**\n * Schema version literal pinned on every persisted\n * {@link JudgePanelVerdict} artifact (Issue #1782, Story MA-3 #1758).\n * Structural changes require a major bump and a `CONTRACT_CHANGELOG.md`\n * entry.\n */\nexport const JUDGE_PANEL_VERDICT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Canonical filename for the per-run Panel-of-LLM-Judges (PoLL)\n * verdict artifact. The harness writes\n * `<runDir>/judge-panel-verdicts.json` once per `semantic_judge`\n * step; the file carries the per-judge raw verdicts the AT-022 audit\n * requires for disagreement reproduction.\n */\nexport const JUDGE_PANEL_VERDICTS_ARTIFACT_FILENAME =\n \"judge-panel-verdicts.json\" as const;\n\n/** Closed runtime list of judge identifiers in the panel. */\nexport const JUDGE_PANEL_JUDGE_IDS = [\n \"judge_primary\",\n \"judge_secondary\",\n] as const;\n\n/** Discriminated alias for {@link JUDGE_PANEL_JUDGE_IDS}. */\nexport type JudgePanelJudgeId = (typeof JUDGE_PANEL_JUDGE_IDS)[number];\n\n/** Closed runtime list of per-judge verdict literals. */\nexport const JUDGE_PANEL_PER_JUDGE_VERDICTS = [\n \"fail\",\n \"pass\",\n \"uncertain\",\n] as const;\n\n/** Discriminated alias for {@link JUDGE_PANEL_PER_JUDGE_VERDICTS}. */\nexport type JudgePanelPerJudgeVerdict =\n (typeof JUDGE_PANEL_PER_JUDGE_VERDICTS)[number];\n\n/** Closed runtime list of panel-level agreement labels. */\nexport const JUDGE_PANEL_AGREEMENT_LABELS = [\n \"both_fail\",\n \"both_pass\",\n \"disagree\",\n] as const;\n\n/** Discriminated alias for {@link JUDGE_PANEL_AGREEMENT_LABELS}. */\nexport type JudgePanelAgreement = (typeof JUDGE_PANEL_AGREEMENT_LABELS)[number];\n\n/**\n * Closed runtime list of resolved-severity labels emitted by the\n * Trust-or-Escalate router. `downgraded_disagreement` is the\n * disagreement-routing severity that downstream consumers can audit\n * against the AT-022 acceptance criterion.\n */\nexport const JUDGE_PANEL_RESOLVED_SEVERITIES = [\n \"critical\",\n \"downgraded_disagreement\",\n \"major\",\n \"minor\",\n] as const;\n\n/** Discriminated alias for {@link JUDGE_PANEL_RESOLVED_SEVERITIES}. */\nexport type JudgePanelResolvedSeverity =\n (typeof JUDGE_PANEL_RESOLVED_SEVERITIES)[number];\n\n/** Closed runtime list of escalation routes the panel may emit. */\nexport const JUDGE_PANEL_ESCALATION_ROUTES = [\n \"accept\",\n \"downgrade\",\n \"needs_review\",\n] as const;\n\n/** Discriminated alias for {@link JUDGE_PANEL_ESCALATION_ROUTES}. */\nexport type JudgePanelEscalationRoute =\n (typeof JUDGE_PANEL_ESCALATION_ROUTES)[number];\n\n/**\n * Hard upper bound on the persisted `reason` text emitted per judge.\n * Reasons longer than this are refused at validation; the limit keeps\n * the artifact bounded and matches the issue spec (≤ 240 chars).\n */\nexport const JUDGE_PANEL_REASON_MAX_CHARS = 240 as const;\n\n/**\n * One judge's pointwise rubric verdict for a single\n * `(testCaseId, criterion)` pair. Raw `score` is the pre-calibration\n * 0..1 score; `calibratedScore` is the post-hoc CalibraEval-style\n * mapping against the fixture distribution. No length normalisation\n * is applied (verbosity-bias inversion 2025).\n */\nexport interface JudgePanelPerJudgeVerdictRecord {\n /** Judge identifier within the panel. */\n readonly judgeId: JudgePanelJudgeId;\n /**\n * Stable model identifier this judge was bound to at the time of\n * scoring. Echoed verbatim from the {@link AgentModelBinding}'s\n * `modelId` (e.g., `\"gpt-oss-120b\"`, `\"phi-4-multimodal-instruct\"`).\n */\n readonly modelBinding: string;\n /** Raw 0..1 pointwise score before post-hoc calibration. */\n readonly score: number;\n /** Post-hoc CalibraEval-style mapped 0..1 score. */\n readonly calibratedScore: number;\n /** Per-judge verdict derived from the calibrated score. */\n readonly verdict: JudgePanelPerJudgeVerdict;\n /**\n * Redacted, length-capped justification (≤\n * {@link JUDGE_PANEL_REASON_MAX_CHARS} chars). Never carries chain\n * of thought, raw prompts, or secrets — the validator refuses\n * over-long or non-string reasons before persistence.\n */\n readonly reason: string;\n}\n\n/**\n * Persisted Panel-of-LLM-Judges (PoLL) verdict for a single\n * `(testCaseId, criterion)` pair (Issue #1782). The harness writes\n * one verdict per scored case; the per-run artifact at\n * `<runDir>/judge-panel-verdicts.json` is a canonical-JSON-stable\n * array of these records (sorted by `(testCaseId, criterion)`).\n *\n * Disagreement routing: `agreement === \"disagree\"` always maps to\n * `escalationRoute ∈ {downgrade, needs_review}` and the resolved\n * severity is downgraded to `downgraded_disagreement` (severity-1) or\n * the case is routed to `needs_review`. Both-pass and both-fail\n * verdicts are deterministic functions of the per-judge verdicts and\n * the panel's escalation policy.\n */\nexport interface JudgePanelVerdict {\n /** Pinned schema version literal. */\n readonly schemaVersion: typeof JUDGE_PANEL_VERDICT_SCHEMA_VERSION;\n /** Test-case identifier the verdict applies to. */\n readonly testCaseId: string;\n /** Stable rubric criterion identifier the verdict scores. */\n readonly criterion: string;\n /**\n * Per-judge raw verdicts, sorted alphabetically by `judgeId` for\n * canonical-JSON stability. Always exactly two entries — the\n * cross-family `judge_primary` and `judge_secondary`.\n */\n readonly perJudge: readonly JudgePanelPerJudgeVerdictRecord[];\n /** Panel-level agreement label derived from per-judge verdicts. */\n readonly agreement: JudgePanelAgreement;\n /** Resolved severity after Trust-or-Escalate routing. */\n readonly resolvedSeverity: JudgePanelResolvedSeverity;\n /** Final routing decision consumed by the Production Runner. */\n readonly escalationRoute: JudgePanelEscalationRoute;\n}\n\n// ---------------------------------------------------------------------------\n// Issue #1898 / #1899 — production-runner logic + faithfulness judges.\n// ---------------------------------------------------------------------------\n\n/** Schema version for persisted logic-judge verdict artifacts. */\nexport const LOGIC_JUDGE_VERDICT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Prompt-template version pinned onto logic-judge verdict artifacts. */\nexport const LOGIC_JUDGE_PROMPT_TEMPLATE_VERSION = \"logic-judge.v1\" as const;\n\n/** Structured-output schema name used by the logic-judge LLM call. */\nexport const LOGIC_JUDGE_OUTPUT_SCHEMA_NAME =\n \"test-intelligence-logic-judge-v1\" as const;\n\n/** Canonical filename for the persisted logic-judge prompt artifact. */\nexport const LOGIC_JUDGE_COMPILED_PROMPT_ARTIFACT_FILENAME =\n \"compiled-prompt-logic-judge.json\" as const;\n\n/** Canonical filename for the persisted logic-judge verdict artifact. */\nexport const LOGIC_JUDGE_VERDICT_ARTIFACT_FILENAME =\n \"logic_judge.json\" as const;\n\n/** Closed runtime list of logic-judge terminal verdicts. */\nexport const ALLOWED_LOGIC_JUDGE_VERDICTS = [\n \"accept\",\n \"repair\",\n \"reject\",\n] as const;\n\n/** Discriminant of an allowed logic-judge terminal verdict. */\nexport type LogicJudgeVerdictLabel =\n (typeof ALLOWED_LOGIC_JUDGE_VERDICTS)[number];\n\n/** Closed runtime list of logic-judge finding severities. */\nexport const ALLOWED_LOGIC_JUDGE_FINDING_SEVERITIES = [\n \"warning\",\n \"error\",\n] as const;\n\n/** Discriminant of an allowed logic-judge finding severity. */\nexport type LogicJudgeFindingSeverity =\n (typeof ALLOWED_LOGIC_JUDGE_FINDING_SEVERITIES)[number];\n\n/**\n * Canonical placeholder used when a finding or repair instruction is scoped to\n * the overall job rather than a single generated test case.\n */\nexport const JOB_LEVEL_TEST_CASE_ID = \"$job\" as const;\n\n/** Closed runtime list of finding scopes used by judge and consensus artifacts. */\nexport const JUDGE_FINDING_SCOPES = [\"job\", \"test_case\"] as const;\n\n/** One logic-judge finding anchored to a generated test case or the job. */\nexport type JudgeFindingScope = (typeof JUDGE_FINDING_SCOPES)[number];\n\n/** One logic-judge finding anchored to a generated test case or the job. */\nexport interface JudgeFinding {\n readonly scope: JudgeFindingScope;\n readonly testCaseId: string;\n readonly code: string;\n readonly severity: LogicJudgeFindingSeverity;\n readonly message: string;\n}\n\n/**\n * One structured repair hint emitted by the logic judge.\n *\n * Paths point at the field that must change. Schema-failure repairs use the\n * same shape so downstream repair consolidators can treat recoverable\n * structured-output violations the same way as semantic repairs.\n */\nexport interface RepairInstruction {\n readonly testCaseId: string;\n readonly path: string;\n readonly instruction: string;\n /** True when `instruction` was clipped to the configured max length. */\n readonly instructionTruncated?: boolean;\n /**\n * Optional structured hint kind. Issue #1931 uses `schema_violation`\n * for deterministic repair-loop guidance when the judge response\n * wrapper fails structured-output validation.\n */\n readonly kind?: \"schema_violation\";\n /**\n * Optional redacted diagnostic paired with {@link kind}. Kept\n * alongside the legacy `instruction` field so existing repair-loop\n * plumbing remains compatible while downstream prompts can consume\n * machine-readable schema-violation metadata.\n */\n readonly message?: string;\n}\n\n/** Optional refusal attached to a logic-judge verdict. */\nexport interface JudgeVerdictRefusal {\n readonly code: string;\n readonly message: string;\n}\n\n/** Persisted logic-judge verdict artifact. */\nexport interface JudgeVerdict {\n readonly schemaVersion: typeof LOGIC_JUDGE_VERDICT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly promptTemplateVersion: typeof LOGIC_JUDGE_PROMPT_TEMPLATE_VERSION;\n readonly generatedAt: string;\n readonly jobId: string;\n readonly cacheHit: boolean;\n readonly cacheKeyDigest: string;\n readonly modelDeployment: string;\n readonly modelRevision: string;\n readonly gatewayRelease: string;\n readonly verdict: LogicJudgeVerdictLabel;\n readonly findings: readonly JudgeFinding[];\n readonly repairInstructions: readonly RepairInstruction[];\n readonly truncatedInstructionCount?: number;\n readonly refusal?: JudgeVerdictRefusal;\n}\n\n/** Schema version for persisted cross-modal faithfulness-judge verdicts.\n *\n * 1.1.0 — Issue #2066: optional additive field `stepVerdicts` on each\n * verdict carrying per-step `match | evidence_partial | mismatch` labels.\n * Backwards compatible: consumers built against 1.0.0 see the legacy\n * `hallucinations` / `mismatches` arrays unchanged. */\nexport const FAITHFULNESS_VERDICT_SCHEMA_VERSION = \"1.1.0\" as const;\n\n/** Prompt-template version pinned onto faithfulness-judge verdict artifacts.\n *\n * `faithfulness-judge.v2` — Issue #2066: prompt rubric distinguishes\n * `match` / `evidence_partial` / `mismatch` per step so partial-evidence\n * signals are no longer collapsed into mismatches.\n *\n * `faithfulness-judge.v3` — Issue #2170: prompt rubric explicitly calls out\n * the state-transition tier (steps belonging to a `state_transition`\n * technique case) so the judge prefers `evidence_partial` over `mismatch`\n * when the workflow transition is intermediate (e.g. only the in-flight\n * frame is captured). The verdict-emission contract is unchanged.\n *\n * `faithfulness-judge.v4` — runtime response schema requires per-step\n * verdicts and the prompt clarifies that screenshots represent the baseline\n * state before test execution, so future user-entered values are partial\n * evidence rather than visible-label mismatches. */\nexport const FAITHFULNESS_JUDGE_PROMPT_TEMPLATE_VERSION =\n \"faithfulness-judge.v4\" as const;\n\n/** Canonical filename for the persisted faithfulness-judge prompt artifact. */\nexport const FAITHFULNESS_JUDGE_COMPILED_PROMPT_ARTIFACT_FILENAME =\n \"compiled-prompt-faithfulness-judge.json\" as const;\n\n/** Canonical filename for the persisted faithfulness-judge verdict artifact. */\nexport const FAITHFULNESS_VERDICT_ARTIFACT_FILENAME =\n \"faithfulness_judge.json\" as const;\n\n/** Closed runtime list of faithfulness-judge terminal verdicts. */\nexport const ALLOWED_FAITHFULNESS_VERDICTS = [\n \"accept\",\n \"repair\",\n \"reject\",\n] as const;\n\n/** Discriminant of an allowed faithfulness-judge terminal verdict. */\nexport type FaithfulnessVerdictLabel =\n (typeof ALLOWED_FAITHFULNESS_VERDICTS)[number];\n\n/** Closed runtime list of per-step faithfulness verdict labels (Issue #2066).\n *\n * - `match` — positive visual evidence for the step.\n * - `evidence_partial` — no contradiction, but the screenshot does not\n * fully verify the step (e.g. label-only step\n * where the description was truncated). Treated\n * as a soft signal, not a mismatch.\n * - `mismatch` — positive contradiction between the step and\n * the screenshot. */\nexport const FAITHFULNESS_STEP_VERDICT_LABELS = [\n \"match\",\n \"evidence_partial\",\n \"mismatch\",\n] as const;\n\n/** Discriminant of an allowed per-step faithfulness verdict. */\nexport type FaithfulnessStepVerdictLabel =\n (typeof FAITHFULNESS_STEP_VERDICT_LABELS)[number];\n\n/** Per-step faithfulness verdict emitted by the cross-family judge.\n *\n * `stepIndex` mirrors `GeneratedTestCaseStep.index` (1-based). `message` is\n * a short reviewer-readable rationale. Persisted verdicts keep the field\n * optional for backwards compatibility with schema 1.0.0 artifacts; new\n * runtime judge calls require it in their response schema. */\nexport interface FaithfulnessStepVerdict {\n readonly testCaseId: string;\n readonly stepIndex: number;\n readonly verdict: FaithfulnessStepVerdictLabel;\n readonly message: string;\n}\n\n/** Hallucination reported by the screenshot-based judge. */\nexport interface HallucinationFinding {\n readonly testCaseId: string;\n readonly stepIndex?: number;\n readonly message: string;\n}\n\n/** Label mismatch reported by the screenshot-based judge. */\nexport interface VisualMismatch {\n readonly testCaseId: string;\n readonly stepIndex?: number;\n readonly expectedLabel: string;\n readonly visibleLabel: string;\n readonly message: string;\n}\n\n/** Optional refusal attached to a faithfulness-judge verdict. */\nexport interface FaithfulnessVerdictRefusal {\n readonly code: string;\n readonly message: string;\n}\n\n/** Persisted screenshot-vs-cases faithfulness verdict artifact. */\nexport interface FaithfulnessVerdict {\n readonly schemaVersion: typeof FAITHFULNESS_VERDICT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly promptTemplateVersion: typeof FAITHFULNESS_JUDGE_PROMPT_TEMPLATE_VERSION;\n readonly generatedAt: string;\n readonly jobId: string;\n readonly cacheHit: boolean;\n readonly cacheKeyDigest: string;\n readonly modelDeployment: string;\n readonly modelRevision: string;\n readonly gatewayRelease: string;\n readonly fallbackReason: VisualSidecarFallbackReason;\n /** Aggregate cross-modal faithfulness score in `[0, 1]`. */\n readonly score: number;\n readonly verdict: FaithfulnessVerdictLabel;\n readonly hallucinations: readonly HallucinationFinding[];\n readonly mismatches: readonly VisualMismatch[];\n /** Per-step verdicts (Issue #2066). Optional for backwards compatibility\n * with verdicts persisted under schema 1.0.0. When emitted, the policy\n * gate uses the per-step labels to compute the tier-aware faithfulness\n * score and the persisted `faithfulness-tier-report.json` artifact. */\n readonly stepVerdicts?: readonly FaithfulnessStepVerdict[];\n readonly refusal?: FaithfulnessVerdictRefusal;\n}\n\n/** Schema version for persisted faithfulness-tier-report artifacts.\n *\n * 1.1.0 — Issue #2170: additive `state_transition` tier label, additive\n * `partialMajorityCaseIds` summary, and richer per-entry `tierReason`. The\n * shape of pre-existing fields is unchanged — readers built against 1.0.0\n * continue to load 1.1.0 reports unchanged. */\nexport const FAITHFULNESS_TIER_REPORT_SCHEMA_VERSION = \"1.1.0\" as const;\n\n/** Canonical filename for the per-run faithfulness-tier-report artifact\n * (Issue #2066). */\nexport const FAITHFULNESS_TIER_REPORT_ARTIFACT_FILENAME =\n \"faithfulness-tier-report.json\" as const;\n\n/** Closed runtime list of step-level faithfulness tiers.\n *\n * - `concrete_data` — the step carries observable input or expected\n * data (numeric value, message text, identifier).\n * Strictness: `match >= 0.95`,\n * `evidence_partial >= 0.80`, `mismatch < 0.80`.\n * - `label_only` — the step asserts label-only or layout-only intent\n * (e.g. \"open the form\", \"see the heading\").\n * Strictness: `match >= 0.95`,\n * `evidence_partial >= 0.85`, `mismatch < 0.85`.\n * - `state_transition` — the step belongs to a `technique === \"state_transition\"`\n * test case AND has no concrete-data assertions\n * (Issue #2170). The cross-family judge can rarely\n * verify the full intermediate frame of a workflow\n * transition from a single capture, so the tier\n * is the most permissive: `match >= 0.95`,\n * `evidence_partial >= 0.65`, `mismatch < 0.65`. */\nexport const FAITHFULNESS_TIER_LABELS = [\n \"concrete_data\",\n \"label_only\",\n \"state_transition\",\n] as const;\n\n/** Discriminant of a step-level faithfulness tier. */\nexport type FaithfulnessTierLabel = (typeof FAITHFULNESS_TIER_LABELS)[number];\n\n/** One per-step entry of the faithfulness-tier-report. */\nexport interface FaithfulnessTierReportEntry {\n readonly testCaseId: string;\n readonly stepIndex: number;\n readonly tier: FaithfulnessTierLabel;\n /** Reviewer-readable indicator that explains the tier classification. */\n readonly tierReason: string;\n readonly verdict: FaithfulnessStepVerdictLabel;\n /** Per-step score `{1.0 | 0.85 | 0.0}` derived from `verdict`. */\n readonly score: number;\n /** Whether the step's `(verdict, score)` clears its tier-aware threshold. */\n readonly passesThreshold: boolean;\n /** Optional reviewer-readable message attached by the judge. */\n readonly message?: string;\n}\n\n/** Closed runtime list of faithfulness-evaluation modes (Issue #2116).\n *\n * - `per_step` — the faithfulness verdict carried per-step\n * verdicts and the gate reasoned over them.\n * Strongest evidence path.\n * - `case_level_fallback` — the verdict was present but lacked\n * `stepVerdicts` (e.g. legacy schema 1.0.0\n * producers); the gate fell back to the\n * verdict's case-level `score`. Auditable\n * fallback.\n * - `missing` — no faithfulness verdict at all; the gate\n * had nothing to reason against.\n */\nexport const FAITHFULNESS_EVALUATION_MODES = [\n \"per_step\",\n \"case_level_fallback\",\n \"missing\",\n] as const;\n\n/** Discriminant of an allowed faithfulness-evaluation mode (Issue #2116). */\nexport type FaithfulnessEvaluationMode =\n (typeof FAITHFULNESS_EVALUATION_MODES)[number];\n\n/** Persistable per-run report capturing the tier path that produced the\n * cross-modal faithfulness score (Issue #2066).\n *\n * The report is emitted on every run that has a non-refused\n * {@link FaithfulnessVerdict}, regardless of whether the gate passes. It\n * lets reviewers audit why a label-only step that was visually only\n * partially verified did NOT trigger a `mismatch`. */\nexport interface FaithfulnessTierReport {\n readonly schemaVersion: typeof FAITHFULNESS_TIER_REPORT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly generatedAt: string;\n readonly jobId: string;\n /** Aggregate cross-modal faithfulness score after tier weighting. */\n readonly aggregateScore: number;\n /** Threshold the aggregate is compared against (profile-scoped). */\n readonly aggregateThreshold: number;\n /** Whether the aggregate score clears the threshold. */\n readonly aggregatePasses: boolean;\n /** Total step count covered by the report. */\n readonly stepCount: number;\n /** Steps where the tier-aware score is `1.0`. */\n readonly matchCount: number;\n /** Steps where the tier-aware score is `0.85`. */\n readonly evidencePartialCount: number;\n /** Steps where the tier-aware score is `0.0`. */\n readonly mismatchCount: number;\n /**\n * Issue #2116 — evaluation mode the report was produced under. The\n * tier-report builder only materializes a report for `per_step` runs\n * (per-step evidence is required to populate `entries`), so this field\n * is constant `\"per_step\"` on persisted reports. The companion\n * {@link TestCasePolicyReport.faithfulnessEvaluation} block surfaces\n * the broader mode taxonomy at the policy-gate level. Carried here so\n * a tier report parsed in isolation tells reviewers unambiguously\n * which evaluation path produced it.\n */\n readonly evaluationMode: FaithfulnessEvaluationMode;\n /** Per-step records, sorted by `(testCaseId, stepIndex)`. */\n readonly entries: readonly FaithfulnessTierReportEntry[];\n /**\n * Issue #2170 — case ids whose `evidence_partial` step verdicts make up\n * `>= 60 %` of the case's verdict population. The companion policy gate\n * raises a per-case `policy:cross-modal-faithfulness-partial-majority`\n * warning (NOT error) for these ids so the case still ships while\n * reviewers see the partial-evidence majority. Sorted ascending; empty\n * when no case crosses the threshold. */\n readonly partialMajorityCaseIds: readonly string[];\n}\n\n/** Issue #2170 — minimum fraction of step verdicts on a case that must be\n * `evidence_partial` for the case to be flagged with the partial-majority\n * warning. Encoded as a constant so the tier report and the policy gate\n * agree on the threshold. */\nexport const FAITHFULNESS_PARTIAL_MAJORITY_FRACTION = 0.6 as const;\n\n/** Schema version for persisted multimodal accessibility-judge verdicts. */\nexport const A11Y_VERDICT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Prompt-template version pinned onto accessibility-judge verdict artifacts. */\nexport const A11Y_JUDGE_PROMPT_TEMPLATE_VERSION = \"a11y-judge.v1\" as const;\n\n/** Structured-output schema name used by the accessibility-judge LLM call. */\nexport const A11Y_JUDGE_OUTPUT_SCHEMA_NAME =\n \"test-intelligence-a11y-judge-v1\" as const;\n\n/** Canonical filename for the persisted accessibility-judge verdict artifact. */\nexport const A11Y_JUDGE_VERDICT_ARTIFACT_FILENAME = \"a11y_judge.json\" as const;\n\n/** Closed runtime list of global accessibility-judge verdicts. */\nexport const ALLOWED_A11Y_VERDICTS = [\"accept\", \"repair\"] as const;\n\n/** Discriminant of an allowed accessibility-judge terminal verdict. */\nexport type A11yJudgeVerdictLabel = (typeof ALLOWED_A11Y_VERDICTS)[number];\n\n/** Closed runtime list of per-criterion accessibility coverage verdicts. */\nexport const ALLOWED_A11Y_CRITERION_VERDICTS = [\n \"covered_passes\",\n \"covered_weakly\",\n \"not_covered\",\n] as const;\n\n/** Discriminant of an accessibility-criterion coverage verdict. */\nexport type A11yCriterionVerdictLabel =\n (typeof ALLOWED_A11Y_CRITERION_VERDICTS)[number];\n\n/** One per-criterion verdict emitted by the multimodal accessibility judge. */\nexport interface A11yCriterionVerdict {\n readonly criterionId: string;\n readonly screenId: string;\n readonly screenName: string;\n readonly pillarId: string;\n readonly successCriterion: string;\n readonly verdict: A11yCriterionVerdictLabel;\n readonly rationale: string;\n}\n\n/** One accessibility-gap finding anchored to a criterion id. */\nexport interface A11yFinding {\n readonly criterionId: string;\n readonly testCaseId: string;\n readonly code: string;\n readonly severity: LogicJudgeFindingSeverity;\n readonly message: string;\n}\n\n/** Optional refusal attached to an accessibility-judge verdict. */\nexport interface A11yVerdictRefusal {\n readonly code: string;\n readonly message: string;\n}\n\n/** Persisted multimodal accessibility-judge verdict artifact. */\nexport interface A11yVerdict {\n readonly schemaVersion: typeof A11Y_VERDICT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly promptTemplateVersion: typeof A11Y_JUDGE_PROMPT_TEMPLATE_VERSION;\n readonly generatedAt: string;\n readonly jobId: string;\n readonly cacheHit: boolean;\n readonly cacheKeyDigest: string;\n readonly modelDeployment: string;\n readonly modelRevision: string;\n readonly gatewayRelease: string;\n readonly verdict: A11yJudgeVerdictLabel;\n readonly criteria: readonly A11yCriterionVerdict[];\n readonly findings: readonly A11yFinding[];\n readonly repairInstructions: readonly RepairInstruction[];\n readonly truncatedInstructionCount?: number;\n readonly refusal?: A11yVerdictRefusal;\n}\n\n/** Schema version for persisted production-runner judge-consensus artifacts. */\nexport const JUDGE_CONSENSUS_SCHEMA_VERSION = \"1.1.0\" as const;\n\n/** Canonical filename for the persisted judge-consensus artifact. */\nexport const JUDGE_CONSENSUS_ARTIFACT_FILENAME =\n \"judge-consensus.json\" as const;\n\n/** Closed runtime list of known production-runner judge ids. */\nexport const KNOWN_JUDGE_CONSENSUS_JUDGE_IDS = [\n \"logic_judge\",\n \"faithfulness_judge\",\n \"hallucination_judge\",\n \"a11y_judge\",\n \"coverage_judge\",\n] as const;\n\n/** Known judge ids surfaced in the production-runner consensus panel. */\nexport type KnownJudgeConsensusJudgeId =\n (typeof KNOWN_JUDGE_CONSENSUS_JUDGE_IDS)[number];\n\n/** Closed runtime list of consensus finding categories. */\nexport const JUDGE_CONSENSUS_FINDING_CATEGORIES = [\n \"schema_class\",\n \"cross_modal_mismatch\",\n \"ir_allowlist_violation\",\n \"hallucination\",\n \"a11y_gap\",\n \"coverage_gap\",\n \"other\",\n] as const;\n\n/** Category tag used by the consensus module for veto / repair routing. */\nexport type JudgeConsensusFindingCategory =\n (typeof JUDGE_CONSENSUS_FINDING_CATEGORIES)[number];\n\n/** One normalized finding consumed by the production-runner consensus module. */\nexport interface JudgeConsensusFinding {\n readonly scope: JudgeFindingScope;\n readonly testCaseId: string;\n readonly code: string;\n readonly message: string;\n readonly severity?: LogicJudgeFindingSeverity;\n readonly category: JudgeConsensusFindingCategory;\n}\n\n/** One normalized judge entry consumed by the consensus module. */\nexport interface JudgeConsensusPanelEntry {\n readonly judgeId: string;\n readonly verdict: LogicJudgeVerdictLabel;\n readonly weight: number;\n /**\n * Optional normalized confidence in the judge verdict (`0..1`).\n * When omitted, callers should treat the vote as fully trusted for\n * backwards compatibility with pre-#2102 artifacts.\n */\n readonly confidence?: number;\n readonly findings: readonly JudgeConsensusFinding[];\n readonly repairInstructions: readonly RepairInstruction[];\n readonly truncatedInstructionCount?: number;\n /**\n * Optional model-family marker (Issue #2038). Set by the harness\n * when the judge is sourced from a known cross-family deployment.\n * Persisted into `judge-consensus.json` so the per-family agreement\n * matrix can be reconstructed offline.\n */\n readonly family?: JudgeModelFamily;\n /**\n * Optional deployment-region marker (Issue #2038). Used by the\n * EU-residency check on the judge-disagreement report.\n */\n readonly region?: JudgeModelRegion;\n /**\n * Optional stable model identifier (e.g., `\"claude-3.5-sonnet-20240620\"`)\n * captured by the harness so the disagreement report can attribute\n * each verdict to a specific model version.\n */\n readonly modelId?: string;\n /**\n * Optional prompt-template version pin captured by the harness so\n * provenance graph edges (B.10) can record every judge family +\n * version used in the run (Issue #2038).\n */\n readonly promptVersion?: string;\n}\n\n/** Primary veto attribution surfaced by the consensus artifact. */\nexport interface JudgeConsensusVeto {\n readonly judgeId: string;\n readonly verdict: LogicJudgeVerdictLabel;\n readonly findingCodes: readonly string[];\n}\n\n/** Vote-shape labels surfaced by the persisted judge-consensus artifact. */\nexport const JUDGE_CONSENSUS_AGREEMENT_SHAPES = [\n \"unanimous\",\n \"majority\",\n \"split\",\n \"vetoed\",\n] as const;\n\n/** Vote-shape labels surfaced by the persisted judge-consensus artifact. */\nexport type JudgeConsensusAgreementShape =\n (typeof JUDGE_CONSENSUS_AGREEMENT_SHAPES)[number];\n\n/** Repair-state labels surfaced by the persisted judge-consensus artifact. */\nexport const JUDGE_CONSENSUS_REPAIR_STATES = [\n \"none\",\n \"repair_required\",\n \"repaired\",\n] as const;\n\n/** Repair-state labels surfaced by the persisted judge-consensus artifact. */\nexport type JudgeConsensusRepairState =\n (typeof JUDGE_CONSENSUS_REPAIR_STATES)[number];\n\n/** Final repair-loop outcomes persisted alongside judge-consensus history. */\nexport const JUDGE_CONSENSUS_REPAIR_OUTCOMES = [\n \"not_needed\",\n \"accepted\",\n \"rejected\",\n \"needs_review\",\n \"convergence_stalled\",\n // Issue #2016: budget_exhausted is a soft outcome — the repair loop\n // refused to start the next regeneration because doing so would have\n // pushed the cumulative generator output tokens or attempts past the\n // FinOps `test_generation` envelope. The latest best-effort case list\n // is still handed downstream so the policy gate can make a final call.\n \"budget_exhausted\",\n] as const;\n\n/** Final repair-loop outcomes persisted alongside judge-consensus history. */\nexport type JudgeConsensusRepairOutcome =\n (typeof JUDGE_CONSENSUS_REPAIR_OUTCOMES)[number];\n\n/** Historical repair metadata persisted alongside the final consensus state. */\nexport interface JudgeConsensusRepairHistory {\n readonly attempted: boolean;\n readonly repairIterationCount: number;\n readonly finalOutcome: JudgeConsensusRepairOutcome;\n readonly historicalFindings: readonly JudgeConsensusFinding[];\n readonly historicalRepairInstructions: readonly RepairInstruction[];\n readonly truncatedInstructionCount?: number;\n}\n\n/** Persisted production-runner judge-consensus artifact. */\nexport interface JudgeConsensusVerdict {\n readonly schemaVersion: typeof JUDGE_CONSENSUS_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly generatedAt: string;\n readonly jobId: string;\n readonly verdict: LogicJudgeVerdictLabel;\n readonly agreementShape: JudgeConsensusAgreementShape;\n readonly repairState: JudgeConsensusRepairState;\n readonly activeFindings: readonly JudgeConsensusFinding[];\n readonly repairInstructions: readonly RepairInstruction[];\n readonly truncatedInstructionCount?: number;\n readonly repairHistory: JudgeConsensusRepairHistory;\n readonly vetoBy?: JudgeConsensusVeto;\n readonly panel: readonly JudgeConsensusPanelEntry[];\n /**\n * Optional cross-family escalation summary (Issue #2038). Populated\n * by `judge-consensus.ts` when the panel triggered a disagreement\n * escalation; consumers (production runner, run-quality reporter)\n * can detect human-review hand-off without re-deriving it from the\n * disagreement-report artifact.\n */\n readonly humanReview?: HumanReviewDecision;\n /**\n * Optional cross-family agreement summary (Issue #2038). Mirrors\n * the totals in `judge-disagreement-report.json` but is inlined here\n * so a single artifact carries both the legal verdict and the\n * disagreement evidence.\n */\n readonly crossFamily?: JudgeCrossFamilySummary;\n}\n\n// ---------------------------------------------------------------------------\n// Issue #2038 — Cross-family judge ensemble + human-review escalation.\n// ---------------------------------------------------------------------------\n\n/**\n * Schema version literal pinned on every persisted\n * {@link JudgeDisagreementReport} artifact (Issue #2038). Structural\n * changes require a major bump and a `CONTRACT_CHANGELOG.md` entry.\n */\nexport const JUDGE_DISAGREEMENT_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Canonical filename for the per-run disagreement-report artifact\n * (Issue #2038). The harness writes\n * `<runDir>/judge-disagreement-report.json` once per `judge_consensus`\n * step. The artifact carries disagreement rate, escalation rate, and\n * the per-family agreement matrix the AT-2038 audit requires.\n */\nexport const JUDGE_DISAGREEMENT_REPORT_ARTIFACT_FILENAME =\n \"judge-disagreement-report.json\" as const;\n\n/**\n * Closed runtime list of cross-family decision shapes (Issue #2038).\n * The vote outcome over the three judge roles maps to one of these\n * labels; the disagreement report tallies them.\n */\nexport const JUDGE_DISAGREEMENT_DECISION_LABELS = [\n \"majority_decision\",\n \"split_decision\",\n \"unanimous_accept\",\n \"unanimous_reject\",\n \"unanimous_repair\",\n] as const;\n\n/** Discriminated alias for {@link JUDGE_DISAGREEMENT_DECISION_LABELS}. */\nexport type JudgeDisagreementDecisionLabel =\n (typeof JUDGE_DISAGREEMENT_DECISION_LABELS)[number];\n\n/**\n * Closed runtime list of escalation actions that the cross-family\n * disagreement detector may emit (Issue #2038). `none` is the\n * no-op; `human_review_required` instructs the consensus builder to\n * attach a deterministic `human_review` envelope to the consensus\n * artifact.\n */\nexport const JUDGE_DISAGREEMENT_ESCALATION_ACTIONS = [\n \"human_review_required\",\n \"none\",\n] as const;\n\n/** Discriminated alias for {@link JUDGE_DISAGREEMENT_ESCALATION_ACTIONS}. */\nexport type JudgeDisagreementEscalationAction =\n (typeof JUDGE_DISAGREEMENT_ESCALATION_ACTIONS)[number];\n\n/** Per-judge entry recorded in the disagreement report. */\nexport interface JudgeDisagreementJudgeEntry {\n /** Stable judge identifier (e.g., `\"logic_judge\"`). */\n readonly judgeId: string;\n /** Judge model-family marker. */\n readonly family: JudgeModelFamily;\n /** Stable model identifier (e.g., `\"claude-3.5-sonnet\"`). */\n readonly modelId: string;\n /** Pinned prompt-template version. */\n readonly promptVersion: string;\n /** Deployment region marker (used by EU-residency policy). */\n readonly region: JudgeModelRegion;\n /** Verdict cast by this judge in the run. */\n readonly verdict: LogicJudgeVerdictLabel;\n}\n\n/** Per-family agreement-matrix cell (Issue #2038). */\nexport interface JudgeDisagreementMatrixCell {\n readonly family: JudgeModelFamily;\n /** Number of votes from this family that matched the resolved verdict. */\n readonly agreements: number;\n /**\n * Number of votes from this family that disagreed with the resolved\n * verdict. Counts every dissenting vote — not just the\n * lone-dissenter case — so a 1:1:1 split surfaces a `dissents: 1`\n * cell on each minority family. The `agreements + dissents === votes`\n * invariant holds per cell.\n */\n readonly dissents: number;\n /** Total votes cast by this family across the run. */\n readonly votes: number;\n}\n\n/** Roll-up cost markers attributed per-family (Issue #2038). */\nexport interface JudgeDisagreementCostByFamily {\n readonly family: JudgeModelFamily;\n /** Total tokens consumed by this family across judge calls. */\n readonly totalTokens: number;\n /** Aggregate cost in micro-units (1e-6 USD); 0 when unknown. */\n readonly costMicrounits: number;\n}\n\n/**\n * Persisted per-run disagreement evidence (Issue #2038). The\n * Production Runner writes one of these per `judge_consensus` step,\n * even when the panel is unanimous — the artifact is the audit anchor\n * the disagreement-rate trending consumes (B.10).\n */\nexport interface JudgeDisagreementReport {\n readonly schemaVersion: typeof JUDGE_DISAGREEMENT_REPORT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly generatedAt: string;\n readonly jobId: string;\n /** Cross-family decision label for this run. */\n readonly decision: JudgeDisagreementDecisionLabel;\n /** Escalation action emitted by the detector. */\n readonly escalation: JudgeDisagreementEscalationAction;\n /** Disagreement rate in [0,1] = dissenting-judge-count / panel-size. */\n readonly disagreementRate: number;\n /** Escalation rate in [0,1] = 1 if escalated, 0 otherwise. */\n readonly escalationRate: number;\n /** Per-judge entries sorted alphabetically by `judgeId`. */\n readonly judges: readonly JudgeDisagreementJudgeEntry[];\n /** Per-family agreement matrix (sorted alphabetically by `family`). */\n readonly perFamilyAgreement: readonly JudgeDisagreementMatrixCell[];\n /** Per-family cost rollup (sorted alphabetically by `family`). */\n readonly costByFamily: readonly JudgeDisagreementCostByFamily[];\n /** Hard guarantee that the artifact never carries a raw prompt. */\n readonly rawPromptsIncluded: false;\n}\n\n/**\n * Schema version literal pinned on every persisted\n * {@link HumanReviewDecision} envelope (Issue #2038).\n */\nexport const HUMAN_REVIEW_DECISION_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Closed runtime list of human-review reviewer kinds (Issue #2038).\n * `dry_run_marker` is the default for offline runs; `principal` is\n * reserved for future integration with the live human-review channel.\n */\nexport const HUMAN_REVIEW_REVIEWER_KINDS = [\n \"dry_run_marker\",\n \"principal\",\n] as const;\n\n/** Discriminated alias for {@link HUMAN_REVIEW_REVIEWER_KINDS}. */\nexport type HumanReviewReviewerKind =\n (typeof HUMAN_REVIEW_REVIEWER_KINDS)[number];\n\n/**\n * Closed runtime list of human-review verdict labels (Issue #2038).\n * The reviewer's verdict feeds back into `JudgeConsensusVerdict` and\n * may override the panel's verdict.\n */\nexport const HUMAN_REVIEW_VERDICT_LABELS = [\n \"accept\",\n \"deferred\",\n \"reject\",\n \"repair\",\n] as const;\n\n/** Discriminated alias for {@link HUMAN_REVIEW_VERDICT_LABELS}. */\nexport type HumanReviewVerdictLabel =\n (typeof HUMAN_REVIEW_VERDICT_LABELS)[number];\n\n/**\n * Hard upper bound on the persisted `rationale` text emitted per\n * human-review decision. Keeps the artifact bounded and prevents\n * smuggling unbounded reviewer prose into the consensus surface.\n */\nexport const HUMAN_REVIEW_RATIONALE_MAX_CHARS = 1024 as const;\n\n/**\n * Persisted human-review decision (Issue #2038). The reviewer is\n * deterministically identified by a `principalHash` (sha256 hex of\n * the reviewer's stable identifier) so the artifact carries a stable\n * audit anchor without leaking reviewer PII.\n */\nexport interface HumanReviewDecision {\n readonly schemaVersion: typeof HUMAN_REVIEW_DECISION_SCHEMA_VERSION;\n /** Reviewer kind — `dry_run_marker` for offline / unattended runs. */\n readonly reviewerKind: HumanReviewReviewerKind;\n /** sha256 hex of the reviewer's stable identifier (64 lowercase hex chars). */\n readonly principalHash: string;\n /** Final verdict the reviewer cast. */\n readonly verdict: HumanReviewVerdictLabel;\n /** Length-capped, redacted rationale (no chain-of-thought, no PII). */\n readonly rationale: string;\n /** ISO-8601 timestamp at which the decision was recorded. */\n readonly decidedAt: string;\n /** Disagreement decision label that triggered escalation. */\n readonly triggeredBy: JudgeDisagreementDecisionLabel;\n}\n\n/**\n * Inline cross-family summary attached to the consensus artifact when\n * the panel was sourced from a cross-family ensemble (Issue #2038).\n */\nexport interface JudgeCrossFamilySummary {\n readonly decision: JudgeDisagreementDecisionLabel;\n readonly escalation: JudgeDisagreementEscalationAction;\n /** Distinct judge model families used in the run, sorted alphabetically. */\n readonly families: readonly JudgeModelFamily[];\n readonly disagreementRate: number;\n readonly escalationRate: number;\n}\n\n// ---------------------------------------------------------------------------\n// Human-review queue + decision-capture surface (Issue #2179).\n//\n// Tier-1 / W6-5 — surfaces the existing `human_review` agent role\n// (Issue #2038) as a queryable per-tenant queue + a signed verdict-capture\n// surface so a competent human operator can satisfy DSGVO Art. 22\n// (\"automated decisions with significant legal effect\") and EU AI Act\n// Art. 14 (\"human oversight\") for banking-test generation runs.\n//\n// Verdicts are signed (ed25519) by the reviewer's key, deterministically\n// hashed via the principal-hash convention from `human-review-agent.ts`,\n// and persisted into:\n//\n// - `human-review-log.json` per run (audit trail).\n// - `provenance.jsonld` (PROV `wasInformedBy` link from the verdict to\n// the original judge-disagreement artifact).\n// - `run-quality.json` (verdict reference attached to the case decision).\n//\n// Replay determinism: re-running a job that previously had a verdict\n// reuses the persisted verdict — no re-prompting the LLM, no re-judging.\n// SLA tracking: every queue item carries `slaDeadlineAt`; expired items\n// surface a `policy:human-review-sla-breach` warning at next run.\n// ---------------------------------------------------------------------------\n\n/** Schema version pinned on every persisted {@link HumanReviewQueueItem}. */\nexport const HUMAN_REVIEW_QUEUE_ITEM_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Schema version pinned on every persisted {@link HumanReviewVerdict}. */\nexport const HUMAN_REVIEW_VERDICT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Schema version pinned on every persisted {@link HumanReviewLog}. */\nexport const HUMAN_REVIEW_LOG_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the per-run human-review audit log. */\nexport const HUMAN_REVIEW_LOG_ARTIFACT_FILENAME =\n \"human-review-log.json\" as const;\n\n/**\n * Canonical filename for the per-run formal-verification report\n * emitted by the LTL / CTL model-checker driver (Issue #2181).\n * Mirrored in `formal-verification.ts` as\n * `FORMAL_VERIFICATION_REPORT_ARTIFACT_FILENAME` — both constants must\n * stay in lockstep.\n */\nexport const FORMAL_VERIFICATION_REPORT_AUDIT_ARTIFACT_FILENAME =\n \"formal-verification-report.json\" as const;\n\n/** Hard upper bound on the persisted reviewer rationale, in characters. */\nexport const HUMAN_REVIEW_VERDICT_RATIONALE_MAX_CHARS = 4096 as const;\n\n/** Closed runtime list of operator-facing review verdict labels. */\nexport const HUMAN_REVIEW_QUEUE_VERDICT_LABELS = [\n \"approved\",\n \"rejected\",\n \"revised\",\n] as const;\n\n/** Discriminated alias for {@link HUMAN_REVIEW_QUEUE_VERDICT_LABELS}. */\nexport type HumanReviewQueueVerdictLabel =\n (typeof HUMAN_REVIEW_QUEUE_VERDICT_LABELS)[number];\n\n/**\n * Closed list of `policy:` warning rules emitted by the human-review\n * surface. Operators can filter the policy report by these stable codes.\n */\nexport const HUMAN_REVIEW_POLICY_WARNING_RULES = [\n \"policy:human-review-sla-breach\",\n] as const;\n\nexport type HumanReviewPolicyWarningRule =\n (typeof HUMAN_REVIEW_POLICY_WARNING_RULES)[number];\n\n/**\n * Inline disagreement snapshot pinned on each queue item. Carries only\n * the deterministic identifying fields from {@link JudgeDisagreementReport}\n * — never raw prompts, never PII — so a queue item can be inspected and\n * replayed without re-loading the full disagreement artifact.\n */\nexport interface JudgeDisagreementSnapshot {\n /** Cross-family decision label that triggered escalation. */\n readonly decision: JudgeDisagreementDecisionLabel;\n /** Escalation action recorded by the disagreement detector. */\n readonly escalation: JudgeDisagreementEscalationAction;\n /** Disagreement rate in [0,1] = dissenting-judge-count / panel-size. */\n readonly disagreementRate: number;\n /** Per-judge entries sorted alphabetically by `judgeId`. */\n readonly judges: readonly JudgeDisagreementJudgeEntry[];\n}\n\n/**\n * One pending case that requires human oversight before the run can\n * advance. The producer (the harness) writes one of these per\n * disagreement-escalated case; the reviewer surface (CLI / minimal UI)\n * fetches them and emits a {@link HumanReviewVerdict}.\n *\n * Hard invariants:\n *\n * - `itemId` is stable across replays for the same logical case so\n * verdicts can be re-located on re-run without an external index.\n * - `tenantId` matches the active {@link TenantScope}; the queue is\n * partitioned per-tenant and never returns cross-tenant items.\n * - `enqueuedAt` and `slaDeadlineAt` are strict ISO-8601 strings; the\n * module is clock-free.\n * - `proposedDecision` records what the harness would have done in the\n * absence of human input — useful for SLA-breach defaulting and for\n * reviewer context.\n */\nexport interface HumanReviewQueueItem {\n readonly schemaVersion: typeof HUMAN_REVIEW_QUEUE_ITEM_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly itemId: string;\n readonly tenantId: string;\n readonly profileId: string;\n readonly runId: string;\n readonly testCaseId: string;\n readonly judgeDisagreement: JudgeDisagreementSnapshot;\n readonly proposedDecision: TestCasePolicyDecision;\n readonly enqueuedAt: string;\n readonly slaDeadlineAt: string;\n}\n\n/**\n * Persisted human verdict for a {@link HumanReviewQueueItem}. The\n * verdict carries a detached ed25519 signature over the canonical-JSON\n * serialisation of the verdict body (every field except `signatureHex`)\n * so the queue can verify reviewer identity and detect tampering before\n * persisting the decision into the run record.\n */\nexport interface HumanReviewVerdict {\n readonly schemaVersion: typeof HUMAN_REVIEW_VERDICT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly itemId: string;\n readonly reviewerPrincipalHash: string;\n readonly verdict: HumanReviewQueueVerdictLabel;\n /** Length-capped, redacted rationale (no chain-of-thought, no PII). */\n readonly rationale: string;\n /**\n * Optional revised test case the reviewer wishes to substitute for the\n * proposed decision. Only meaningful when `verdict === \"revised\"`.\n * Stored as an opaque JSON object — the queue does not validate the\n * inner `GeneratedTestCase` shape (the consumer does).\n */\n readonly revisedTestCase?: Readonly<Record<string, unknown>>;\n readonly decidedAt: string;\n /** Detached ed25519 signature, lowercase hex (128 chars). */\n readonly signatureHex: string;\n /** SPKI-DER sha256 hex of the reviewer's public key. */\n readonly publicKeyFingerprintSha256: string;\n /** PEM-encoded SPKI public key the signature can be verified against. */\n readonly publicKeyPem: string;\n}\n\n/** Filter shape consumed by the queue-fetch surface. */\nexport interface HumanReviewFilter {\n readonly tenantId: string;\n readonly profileId?: string;\n /**\n * Optional inclusive upper bound on `slaDeadlineAt`. ISO-8601 string;\n * items with `slaDeadlineAt <= slaDueBy` are returned.\n */\n readonly slaDueBy?: string;\n}\n\n/**\n * Per-run audit log capturing the queue items that were created during\n * the run and any verdicts that were recorded against them. The log is\n * canonical-JSON, byte-stable for byte-identical inputs, and bundled\n * into the W6-1 audit-dossier (Issue #2175) so a regulator can replay\n * the human-oversight chain.\n */\nexport interface HumanReviewLog {\n readonly schemaVersion: typeof HUMAN_REVIEW_LOG_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly jobId: string;\n readonly tenantId: string;\n readonly generatedAt: string;\n /** Items sorted alphabetically by `itemId`. */\n readonly items: readonly HumanReviewQueueItem[];\n /** Verdicts sorted alphabetically by `itemId`. */\n readonly verdicts: readonly HumanReviewVerdict[];\n /**\n * Items whose `slaDeadlineAt` had elapsed at log-emission time.\n * Drives the `policy:human-review-sla-breach` warning on the next run.\n */\n readonly slaBreaches: readonly HumanReviewSlaBreachEntry[];\n}\n\n/** One SLA-breach entry surfaced for the next run's policy report. */\nexport interface HumanReviewSlaBreachEntry {\n readonly itemId: string;\n readonly testCaseId: string;\n readonly slaDeadlineAt: string;\n readonly observedAt: string;\n}\n\n/** Schema version for persisted production-runner run-quality artifacts. */\nexport const RUN_QUALITY_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted run-quality artifact. */\nexport const RUN_QUALITY_ARTIFACT_FILENAME = \"run-quality.json\" as const;\n\n/** Top-level run-quality states for the production-runner artifact bundle. */\nexport const RUN_QUALITY_STATUSES = [\n \"clean_success\",\n \"repaired_success\",\n \"degraded_success\",\n \"blocked_failure\",\n] as const;\n\n/** Top-level run-quality states for the production-runner artifact bundle. */\nexport type RunQualityStatus = (typeof RUN_QUALITY_STATUSES)[number];\n\n/** Stage identifiers surfaced in the run-quality attempt summaries. */\nexport const RUN_QUALITY_STAGE_IDS = [\n \"generator\",\n \"judge\",\n \"visual_sidecar\",\n \"policy_gate\",\n] as const;\n\n/** Stage identifiers surfaced in the run-quality attempt summaries. */\nexport type RunQualityStageId = (typeof RUN_QUALITY_STAGE_IDS)[number];\n\n/** Stage-level attempt summary persisted in the run-quality artifact. */\nexport interface RunQualityAttemptSummary {\n readonly stage: RunQualityStageId;\n readonly attempts: number;\n readonly successes: number;\n readonly failures: number;\n readonly finalOutcome:\n | \"not_run\"\n | \"clean\"\n | \"recovered\"\n | \"degraded\"\n | \"blocked\";\n readonly lastErrorClass?: string;\n}\n\n/** Persisted production-runner run-quality artifact. */\nexport interface RunQualityArtifact {\n readonly schemaVersion: typeof RUN_QUALITY_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly generatedAt: string;\n readonly jobId: string;\n readonly status: RunQualityStatus;\n readonly blocked: boolean;\n readonly usable: boolean;\n readonly finalJudgeVerdict: LogicJudgeVerdictLabel;\n readonly repairState: JudgeConsensusRepairState;\n readonly repairHistory: JudgeConsensusRepairHistory;\n readonly activeFindings: readonly JudgeConsensusFinding[];\n readonly activeFindingCount: number;\n readonly attemptSummaries: readonly RunQualityAttemptSummary[];\n readonly degradedReasons: readonly string[];\n /** Aggregate self-consistency agreement ratio in [0,1], when sampled. */\n readonly selfConsistencyAgreement?: number;\n}\n\n/** Schema version for per-run self-consistency voting artifacts. */\nexport const SELF_CONSISTENCY_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted self-consistency artifact. */\nexport const SELF_CONSISTENCY_REPORT_ARTIFACT_FILENAME =\n \"self-consistency-report.json\" as const;\n\n/** Stable route emitted for targets whose samples disagree structurally. */\nexport type SelfConsistencyDisagreementRoute =\n | \"cross_family_arbitration\"\n | \"human_review\";\n\n/** Deterministic self-consistency vote summary (Issue #2125). */\nexport interface SelfConsistencyVote {\n readonly winner?: string;\n readonly agreementRate: number;\n readonly confidenceInterval95: readonly [number, number];\n readonly bootstrapSampleSize: number;\n readonly consensusStrength: \"strong_consensus\" | \"weak_consensus\";\n}\n\n/** One field-level vote recorded for a single coverage target. */\nexport interface SelfConsistencyFieldVote extends SelfConsistencyVote {\n readonly field:\n | \"type\"\n | \"technique\"\n | \"riskCategory\"\n | \"step_action\"\n | \"step_expected\";\n readonly stepIndex?: number;\n /** Backwards-compatible alias for agreementRate. */\n readonly agreement: number;\n readonly majorityValue?: string;\n readonly majorityCount: number;\n}\n\n/** One per-target row in the persisted self-consistency report. */\nexport interface SelfConsistencyTargetReportEntry {\n readonly targetKey: string;\n readonly selectedTestCaseId: string;\n readonly samplePresenceCount: number;\n readonly agreement: number;\n readonly consensusStrength: \"strong_consensus\" | \"weak_consensus\";\n readonly disagreement: boolean;\n readonly disagreementRoute?: SelfConsistencyDisagreementRoute;\n readonly arbitrationTriggered?: boolean;\n readonly votes: readonly SelfConsistencyFieldVote[];\n}\n\n/** Persisted self-consistency voting artifact (Issue #2070). */\nexport interface SelfConsistencyReport {\n readonly schemaVersion: typeof SELF_CONSISTENCY_REPORT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly generatedAt: string;\n readonly jobId: string;\n readonly sampleCount: number;\n readonly selfConsistencyAgreement: number;\n readonly targets: readonly SelfConsistencyTargetReportEntry[];\n}\n\n/** Canonical filename for per-run genealogy DAG artifacts. */\nexport const GENEALOGY_ARTIFACT_FILENAME = \"genealogy.json\" as const;\n\n/** Schema version for per-run genealogy DAG artifacts. */\nexport const GENEALOGY_SCHEMA_VERSION = \"1.0.0\" as const;\n\n// ---------------------------------------------------------------------------\n// Issue #1795 — canonical-JSON harness job artifacts\n//\n// The harness persists every report it produces under the per-job runDir as\n// canonical-JSON (or canonical newline-delimited JSON for append-mostly\n// event logs). All artifacts below are atomic-write (tmp + rename),\n// byte-stable for byte-identical inputs, and schema-versioned. They are\n// hashed into a sibling {@link HarnessArtifactManifest} so the evidence\n// verify route can reproduce every hash offline without re-running the\n// harness.\n// ---------------------------------------------------------------------------\n\n/** Canonical filename for the consolidated per-repair-iteration log. */\nexport const AGENT_ITERATIONS_ARTIFACT_FILENAME =\n \"agent-iterations.json\" as const;\n\n/** Schema version for {@link AgentIterationsArtifact}. */\nexport const AGENT_ITERATIONS_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Closed runtime list of repair-iteration outcome literals. */\nexport const ALLOWED_AGENT_ITERATION_OUTCOMES = [\n \"exhausted\",\n \"halted\",\n \"needs_repair\",\n \"passed\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_AGENT_ITERATION_OUTCOMES}. */\nexport type AgentIterationOutcome =\n (typeof ALLOWED_AGENT_ITERATION_OUTCOMES)[number];\n\n/** One persisted record describing a single repair-iteration step. */\nexport interface AgentIterationRecord {\n /** 0-based iteration index inside the repair budget. */\n readonly iteration: number;\n /** Stable harness role-step identifier of the role that ran. */\n readonly roleStepId: string;\n /** ISO-8601 timestamp at which the iteration started. */\n readonly startedAt: string;\n /** ISO-8601 timestamp at which the iteration completed. */\n readonly completedAt: string;\n /** Resolved outcome of the iteration. */\n readonly outcome: AgentIterationOutcome;\n /** Total finding count surfaced by this iteration. */\n readonly findingsCount: number;\n /** Optional repair-plan identifier carried into the next iteration. */\n readonly repairPlanId?: string;\n /** Merkle parent hash linking this iteration into the harness chain. */\n readonly parentHash: string;\n}\n\n/** Persisted, canonical-JSON, per-job repair-iteration log. */\nexport interface AgentIterationsArtifact {\n readonly schemaVersion: typeof AGENT_ITERATIONS_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly jobId: string;\n /** ISO-8601 timestamp the artifact was assembled (server clock). */\n readonly generatedAt: string;\n /** Iteration records sorted by `iteration` ascending. */\n readonly iterations: readonly AgentIterationRecord[];\n}\n\n/** Canonical filename for the consolidated cache-break event log. */\nexport const CACHE_BREAK_EVENTS_LOG_ARTIFACT_FILENAME =\n \"cache-break-events.jsonl\" as const;\n\n/** Schema version for {@link CacheBreakEventLogEntry}. */\nexport const CACHE_BREAK_EVENTS_LOG_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * One persisted line in `cache-break-events.jsonl`. Each line is an\n * independent canonical-JSON object so the file is byte-stable when the\n * input set is byte-identical and the entries are sorted before write.\n */\nexport interface CacheBreakEventLogEntry {\n readonly schemaVersion: typeof CACHE_BREAK_EVENTS_LOG_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly jobId: string;\n readonly roleStepId: string;\n readonly querySource: string;\n /** ISO-8601 timestamp at which the break was observed. */\n readonly ts: string;\n /** Merkle parent hash carried by the original `cache_break` event. */\n readonly parentHash: string;\n /** `cache_read_input_tokens` observed on the breaking response. */\n readonly cacheReadTokens: number;\n /** `cache_creation_input_tokens` observed on the breaking response. */\n readonly cacheCreationTokens: number;\n /** Basename of the per-event diff artifact, when persisted. */\n readonly diffArtifactBasename?: string;\n /** Suppression reason when the break was intentional, if any. */\n readonly suppressionReason?: CacheBreakSuppressionReason;\n}\n\n/** Canonical filename for the consolidated CompactBoundary log. */\nexport const COMPACT_BOUNDARY_LOG_ARTIFACT_FILENAME =\n \"compact-boundary-log.jsonl\" as const;\n\n/** Schema version for {@link CompactBoundaryLogEntry}. */\nexport const COMPACT_BOUNDARY_LOG_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Closed runtime list of compaction-boundary tier literals. */\nexport const ALLOWED_COMPACT_BOUNDARY_LOG_TIERS = [\n \"context_budget\",\n \"manual\",\n \"post_repair\",\n \"task_round\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_COMPACT_BOUNDARY_LOG_TIERS}. */\nexport type CompactBoundaryLogTier =\n (typeof ALLOWED_COMPACT_BOUNDARY_LOG_TIERS)[number];\n\n/**\n * One persisted line in `compact-boundary-log.jsonl`. Carries only\n * non-sensitive identifiers (sha256 of the summary, byte-counts) so the\n * log can be persisted alongside the per-job artifacts without leaking\n * raw conversation text.\n */\nexport interface CompactBoundaryLogEntry {\n readonly schemaVersion: typeof COMPACT_BOUNDARY_LOG_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly jobId: string;\n /** ISO-8601 timestamp of the compaction boundary. */\n readonly ts: string;\n /** Tier the boundary belongs to. */\n readonly tier: CompactBoundaryLogTier;\n /** sha256-hex of the canonical-JSON summary string. */\n readonly summarySha256: string;\n /** Total bytes of cleared tool result blocks at the boundary. */\n readonly clearedToolResultBytes: number;\n /** Merkle parent hash linking this boundary into the harness chain. */\n readonly parentHash: string;\n}\n\n/** Canonical filename for the per-run settings migration audit log. */\nexport const MIGRATIONS_LOG_ARTIFACT_FILENAME = \"migrations.log.jsonl\" as const;\n\n/** Schema version for approved signed migration bundles. */\nexport const MIGRATION_BUNDLE_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Closed runtime list of migration refusal codes. */\nexport const ALLOWED_MIGRATION_REFUSAL_CODES = [\n \"migration_apply_failed\",\n \"migration_audit_log_invalid\",\n \"migration_registry_invalid\",\n \"migration_rollback_failed\",\n \"migration_rollback_required\",\n \"migration_state_invalid\",\n \"migration_unsigned\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_MIGRATION_REFUSAL_CODES}. */\nexport type MigrationRefusalCode =\n (typeof ALLOWED_MIGRATION_REFUSAL_CODES)[number];\n\n/** One approved entry inside a signed migration bundle. */\nexport interface SignedMigrationBundleEntry {\n readonly id: string;\n readonly hash: string;\n readonly description: string;\n readonly evidenceBearing?: boolean;\n}\n\n/** Changelog-approved signed migration bundle for banking-profile runs. */\nexport interface SignedMigrationBundle {\n readonly schemaVersion: typeof MIGRATION_BUNDLE_SCHEMA_VERSION;\n readonly contractVersion: typeof CONTRACT_VERSION;\n readonly entries: readonly SignedMigrationBundleEntry[];\n}\n\n/** Canonical filename for the per-release library coverage report. */\nexport const LIBRARY_COVERAGE_REPORT_ARTIFACT_FILENAME =\n \"library-coverage-report.json\" as const;\n\n/** Schema version for {@link LibraryCoverageReport}. */\nexport const LIBRARY_COVERAGE_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Closed runtime list of library-primitive status literals. */\nexport const ALLOWED_LIBRARY_PRIMITIVE_STATUSES = [\n \"deprecated\",\n \"implemented\",\n \"stub\",\n \"unimplemented\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_LIBRARY_PRIMITIVE_STATUSES}. */\nexport type LibraryPrimitiveStatus =\n (typeof ALLOWED_LIBRARY_PRIMITIVE_STATUSES)[number];\n\n/** Per-primitive coverage row in {@link LibraryCoverageReport.primitives}. */\nexport interface LibraryPrimitiveCoverageEntry {\n /** Stable identifier of the primitive within the library version. */\n readonly primitiveId: string;\n /** Library name (e.g. design-system or component-library). */\n readonly libraryName: string;\n /** Library version — must match the release's pinned version. */\n readonly libraryVersion: string;\n /** Status of the primitive in this release. */\n readonly status: LibraryPrimitiveStatus;\n /** Number of generated test cases that exercise this primitive. */\n readonly testCaseCount: number;\n /** Optional human-readable note (length-capped at validation). */\n readonly notes?: string;\n}\n\n/** Roll-up counts in {@link LibraryCoverageReport.counts}. */\nexport interface LibraryCoverageReportCounts {\n readonly total: number;\n readonly deprecated: number;\n readonly implemented: number;\n readonly stub: number;\n readonly unimplemented: number;\n}\n\n/** Per-release primitive-map status report. */\nexport interface LibraryCoverageReport {\n readonly schemaVersion: typeof LIBRARY_COVERAGE_REPORT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n /** Stable release identifier (e.g. `\"figma-ds@2026.05.0\"`). */\n readonly releaseId: string;\n /** ISO-8601 timestamp the report was assembled. */\n readonly generatedAt: string;\n /**\n * Per-primitive rows, sorted by `(libraryName, libraryVersion,\n * primitiveId)` for canonical-JSON stability.\n */\n readonly primitives: readonly LibraryPrimitiveCoverageEntry[];\n /** Roll-up counts, derived from `primitives`. */\n readonly counts: LibraryCoverageReportCounts;\n}\n\n/** Canonical filename for the per-job harness artifact manifest. */\nexport const HARNESS_ARTIFACT_MANIFEST_ARTIFACT_FILENAME =\n \"harness-artifact-manifest.json\" as const;\n\n/** Schema version for {@link HarnessArtifactManifest}. */\nexport const HARNESS_ARTIFACT_MANIFEST_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Closed runtime list of canonical-JSON harness artifact filenames the\n * manifest may reference. Adding a member is an additive minor bump.\n */\nexport const ALLOWED_HARNESS_ARTIFACT_FILENAMES = [\n \"agent-findings.json\",\n \"agent-iterations.json\",\n \"cache-break-events.jsonl\",\n \"compact-boundary-log.jsonl\",\n \"coverage-plan.json\",\n // Issue #2128 — per-job differential-privacy budget-accounting manifest\n // (training-influence accountability). Opt-in feature; written only when\n // the policy profile enables `trainingInfluenceDpBudget`.\n \"dp-budget-consumed.json\",\n \"genealogy.json\",\n \"ir-mutation-coverage-strength.json\",\n \"judge-panel-verdicts.json\",\n \"library-coverage-report.json\",\n \"migrations.log.jsonl\",\n \"self-verify-rubric.json\",\n \"test-design-model.json\",\n \"workflow-topology.json\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_HARNESS_ARTIFACT_FILENAMES}. */\nexport type HarnessArtifactFilename =\n (typeof ALLOWED_HARNESS_ARTIFACT_FILENAMES)[number];\n\n/** One row of {@link HarnessArtifactManifest.entries}. */\nexport interface HarnessArtifactManifestEntry {\n /** Basename of the artifact, relative to the per-job runDir. */\n readonly filename: HarnessArtifactFilename;\n /** Schema version literal of the artifact at the time of write. */\n readonly schemaVersion: string;\n /** sha256-hex of the on-disk artifact bytes. */\n readonly sha256: string;\n /** Total byte length of the on-disk artifact. */\n readonly sizeBytes: number;\n}\n\n/**\n * Per-job manifest of canonical-JSON harness artifacts. Persisted as\n * `harness-artifact-manifest.json` next to the artifacts it indexes.\n * The {@link HarnessArtifactManifest.digest} is a sha256 over the\n * canonical-JSON of the sorted `entries` array, so the evidence verify\n * route can reproduce every artifact hash offline by re-reading the\n * referenced files and recomputing each row.\n */\nexport interface HarnessArtifactManifest {\n readonly schemaVersion: typeof HARNESS_ARTIFACT_MANIFEST_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly jobId: string;\n readonly generatedAt: string;\n /** Entries sorted by `filename` ascending. */\n readonly entries: readonly HarnessArtifactManifestEntry[];\n /** sha256-hex over `canonicalJson(entries)`. */\n readonly digest: string;\n}\n\n/**\n * Issue #2128 — schema version for {@link DpBudgetConsumedManifest}.\n */\nexport const DP_BUDGET_CONSUMED_MANIFEST_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Issue #2128 — canonical filename for the per-job differential-privacy\n * budget-accounting manifest. Written into the per-job runDir when the\n * policy profile enables `trainingInfluenceDpBudget`. The filename is also\n * a member of {@link ALLOWED_HARNESS_ARTIFACT_FILENAMES} so the harness\n * artifact manifest pins its sha256 + size for audit replay.\n */\nexport const DP_BUDGET_CONSUMED_MANIFEST_ARTIFACT_FILENAME =\n \"dp-budget-consumed.json\" as const;\n\n/**\n * Issue #2128 — default per-input-token epsilon estimate used by\n * `estimateJobDpCharge`. Conservative ceiling chosen so a 200 000-token job\n * (the `eu-banking-default` `test_generation` request cap) is charged\n * `epsilon = 20` against the tenant budget. Operators tighten this on a\n * derived policy profile when contractually required.\n */\nexport const DP_BUDGET_DEFAULT_PER_TOKEN_EPSILON = 1e-4 as const;\n\n/**\n * Issue #2128 — default per-job delta estimate. Constant per job (does not\n * scale with token count) per the additive-composition accounting model\n * documented in the ADR.\n */\nexport const DP_BUDGET_DEFAULT_DELTA_PER_JOB = 1e-6 as const;\n\n/**\n * Issue #2128 — closed list of decisions `applyDpCharge` can return.\n * Adding a member is an additive minor bump.\n */\nexport const ALLOWED_DP_BUDGET_DECISIONS = [\n \"accepted\",\n \"rejected_budget_exhausted\",\n \"skipped_disabled\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_DP_BUDGET_DECISIONS}. */\nexport type DpBudgetDecision = (typeof ALLOWED_DP_BUDGET_DECISIONS)[number];\n\n/**\n * Issue #2128 — operator-controlled training-influence DP budget policy.\n * Opt-in: when the policy profile omits `trainingInfluenceDpBudget` (the\n * default) the gate is fully inactive and no manifest is written.\n *\n * The numbers in this config are NOT cryptographic differential-privacy\n * guarantees — they are an operator-facing accounting layer that bounds\n * how much input content a tenant may contribute to provider gateways\n * inside a single cycle. See the ADR for the mathematical model.\n */\nexport interface TrainingInfluenceDpBudgetConfig {\n /** Master switch. When `false`, the gate is inactive. */\n readonly enabled: boolean;\n /**\n * Epsilon contribution charged per input token. Default\n * {@link DP_BUDGET_DEFAULT_PER_TOKEN_EPSILON}.\n */\n readonly perTokenEpsilon?: number;\n /**\n * Delta contribution charged per job (constant, independent of token\n * count). Default {@link DP_BUDGET_DEFAULT_DELTA_PER_JOB}.\n */\n readonly deltaPerJob?: number;\n /** Tenant-level epsilon cap across all jobs in the current cycle. */\n readonly tenantEpsilonBudget: number;\n /** Tenant-level delta cap across all jobs in the current cycle. */\n readonly tenantDeltaBudget: number;\n}\n\n/**\n * Issue #2128 — durable per-tenant accounting state. The harness reads\n * this before each job, calls `applyDpCharge`, and persists the\n * returned `newState` for the next job. `cycleId` is opaque to the\n * accountant — operators advance it on their preferred cadence (per\n * day, per quarter, per audit window) by calling\n * `resetTenantDpBudgetCycle`.\n */\nexport interface TenantDpBudgetState {\n readonly schemaVersion: typeof DP_BUDGET_CONSUMED_MANIFEST_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly tenantId: string;\n readonly cycleId: string;\n readonly cycleStartedAt: string;\n readonly epsilonBudget: number;\n readonly deltaBudget: number;\n readonly epsilonConsumed: number;\n readonly deltaConsumed: number;\n readonly jobsCharged: number;\n}\n\n/**\n * Issue #2128 — estimated per-job charge produced by\n * `estimateJobDpCharge`. The values are deterministic functions of the\n * inputs; two byte-identical jobs estimate to byte-identical charges.\n */\nexport interface DpBudgetCharge {\n readonly epsilon: number;\n readonly delta: number;\n readonly inputTokens: number;\n readonly perTokenEpsilon: number;\n readonly deltaPerJob: number;\n}\n\n/**\n * Issue #2128 — per-job manifest carrying `dpBudgetConsumed` for audit.\n * Persisted as `dp-budget-consumed.json` next to the other harness\n * artifacts. The {@link HarnessArtifactManifest} pins its sha256 + size\n * so the evidence-verify route reproduces the audit trail offline.\n */\nexport interface DpBudgetConsumedManifest {\n readonly schemaVersion: typeof DP_BUDGET_CONSUMED_MANIFEST_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly tenantId: string;\n readonly jobId: string;\n readonly cycleId: string;\n readonly generatedAt: string;\n readonly decision: DpBudgetDecision;\n /** Charge applied to the tenant budget by this job. */\n readonly dpBudgetConsumed: {\n readonly epsilon: number;\n readonly delta: number;\n readonly inputTokens: number;\n };\n /** Running cycle totals AFTER this job's charge was applied. */\n readonly cycleTotals: {\n readonly epsilonConsumed: number;\n readonly deltaConsumed: number;\n readonly epsilonBudget: number;\n readonly deltaBudget: number;\n readonly jobsCharged: number;\n };\n /** Parameters that produced the estimate, for replay. */\n readonly parameters: {\n readonly perTokenEpsilon: number;\n readonly deltaPerJob: number;\n };\n}\n\n/**\n * Known PII-like categories detected in mock form data and Jira payloads.\n *\n * Wave 4.B (Issue #1432) extended this union with three Jira-aware\n * categories: `internal_hostname` (corporate hostname patterns surfaced\n * inside ADF text), `jira_mention` (Confluence/Jira `@user` mentions and\n * raw account ids), and `customer_name_placeholder` (full-name-shaped\n * values pulled from common Jira customer-facing custom-field names).\n *\n * Adding new union members is treated as a minor contract bump per\n * `CONTRACT_CHANGELOG.md`'s versioning rules — consumers reading the IR\n * may receive previously-unseen `kind` values.\n */\nexport type PiiKind =\n | \"iban\"\n | \"bic\"\n | \"pan\"\n | \"tax_id\"\n | \"email\"\n | \"phone\"\n | \"full_name\"\n | \"internal_hostname\"\n | \"jira_mention\"\n | \"customer_name_placeholder\"\n // Issue #1668 (audit-2026-05): GDPR Art. 5(1)(c) data-minimization\n // categories that the May 2026 audit identified as previously\n // uncovered. Each is a hand-rolled detector (no runtime deps per\n // repo policy). Adding union members is a minor contract bump per\n // CONTRACT_CHANGELOG.md.\n | \"postal_address\"\n | \"date_of_birth\"\n | \"account_number\"\n | \"national_id\"\n | \"special_category\";\n\n/** Where a detected element came from during reconciliation. */\nexport type IntentProvenance = \"figma_node\" | \"visual_sidecar\" | \"reconciled\";\n\n/**\n * Location within the input that held a PII-like match.\n *\n * Wave 4.B (Issue #1432) extends this union with Jira-IR-specific\n * locations so adversarial-fixture and audit code can attribute every\n * indicator back to the exact field it was sourced from.\n */\nexport type PiiMatchLocation =\n | \"field_label\"\n | \"field_default_value\"\n | \"screen_text\"\n | \"action_label\"\n | \"trace_node_name\"\n | \"trace_node_path\"\n | \"screen_name\"\n | \"screen_path\"\n | \"validation_rule\"\n | \"navigation_target\"\n | \"jira_summary\"\n | \"jira_description\"\n | \"jira_acceptance_criterion\"\n | \"jira_comment_body\"\n | \"jira_custom_field_name\"\n | \"jira_custom_field_value\"\n | \"jira_attachment_filename\"\n | \"jira_link_relationship\"\n | \"jira_label\"\n | \"jira_component\"\n | \"custom_context_markdown\"\n | \"custom_context_attribute\";\n\n/**\n * Reference to the Figma node that produced an intent element.\n *\n * Wave 4 (Issue #1431) extends this trace with an optional array of\n * contributing {@link TestIntentSourceRef} entries so a single trace may\n * record multiple sources that agreed on (or conflicted over) a field.\n * The legacy `nodeId` / `nodeName` / `nodePath` fields keep working for\n * single-source Figma traces.\n */\nexport interface IntentTraceRef {\n nodeId?: string;\n nodeName?: string;\n nodePath?: string;\n /**\n * Contributing source references for this trace (Issue #1431).\n * Optional — omitted for legacy single-source Figma jobs to keep\n * artifacts byte-stable. When present, each entry MUST match an entry\n * in the surrounding {@link MultiSourceTestIntentEnvelope.sources}\n * array by `sourceId`.\n */\n sourceRefs?: TestIntentSourceRef[];\n /**\n * Original Figma component name for an instance/component-shaped node\n * whose visible label was synthesised from a descendant TEXT node\n * (Issue #1902). Provenance-only — preserved so a downstream judge can\n * always recover the raw component identity.\n */\n componentName?: string;\n}\n\n/** Ambiguity note attached to a detected element or PII indicator. */\nexport interface IntentAmbiguity {\n reason: string;\n}\n\n/** PII indicator attached to a detected element. Original values are never persisted. */\nexport interface PiiIndicator {\n id: string;\n kind: PiiKind;\n confidence: number;\n matchLocation: PiiMatchLocation;\n redacted: string;\n screenId?: string;\n elementId?: string;\n traceRef?: IntentTraceRef;\n}\n\n/** Record describing a single redaction decision. */\nexport interface IntentRedaction {\n id: string;\n indicatorId: string;\n kind: PiiKind;\n reason: string;\n replacement: string;\n}\n\n/**\n * Input field inferred from a screen.\n *\n * Wave 4 (Issue #1431) adds the optional `sourceRefs` array so the\n * derivation pipeline can record every source that contributed to this\n * field. The legacy singular `trace` and `provenance` fields keep\n * working unchanged for single-source jobs.\n */\nexport interface DetectedField {\n id: string;\n screenId: string;\n trace: IntentTraceRef;\n provenance: IntentProvenance;\n confidence: number;\n label: string;\n type: string;\n defaultValue?: string;\n ambiguity?: IntentAmbiguity;\n /** Contributing sources (Issue #1431). Optional, additive. */\n sourceRefs?: TestIntentSourceRef[];\n /**\n * How the visible label was derived (Issue #1902). Optional, additive.\n * - `node_text`: TEXT node `characters` (default).\n * - `node_name`: fallback to the Figma node name.\n * - `sibling_text`: synthesised from a spatially-paired TEXT node.\n */\n labelSource?: LabelSource;\n /**\n * Confidence in the synthesised label (Issue #1902). Optional, additive.\n * `0` signals a generic/weak label that downstream judges may treat as a\n * `weak_label` finding. Omitted for trivially derived labels.\n */\n labelConfidence?: number;\n /**\n * Spatial cluster id (Issue #1902). Optional, additive. Fields whose bboxes\n * sit close together (label/value pairs, summary blocks) share the same\n * cluster id so the generator can write coherent cases.\n */\n clusterId?: string;\n}\n\n/**\n * Action/control inferred from a screen (e.g. Submit button).\n *\n * Wave 4 (Issue #1431) adds the optional `sourceRefs` array; see\n * {@link DetectedField} for backward-compat semantics.\n */\nexport interface DetectedAction {\n id: string;\n screenId: string;\n trace: IntentTraceRef;\n provenance: IntentProvenance;\n confidence: number;\n label: string;\n kind: string;\n ambiguity?: IntentAmbiguity;\n /** Contributing sources (Issue #1431). Optional, additive. */\n sourceRefs?: TestIntentSourceRef[];\n /**\n * How the visible label was derived (Issue #1902). Optional, additive.\n * See {@link DetectedField.labelSource} for semantics.\n */\n labelSource?: LabelSource;\n /**\n * Confidence in the synthesised label (Issue #1902). Optional, additive.\n * `0` signals a generic component-instance label (e.g. `<Button>`) that\n * could not be paired with a sibling TEXT node — downstream judges may\n * surface this as a `weak_label` finding.\n */\n labelConfidence?: number;\n}\n\n/**\n * Origin of the visible label for a detected element (Issue #1902).\n * Closed enum so judge code can pattern-match safely without sniffing strings.\n */\nexport type LabelSource = \"node_text\" | \"node_name\" | \"sibling_text\";\n\n/**\n * Validation rule inferred from design hints.\n *\n * Wave 4 (Issue #1431) adds the optional `sourceRefs` array; see\n * {@link DetectedField} for backward-compat semantics.\n */\nexport interface DetectedValidation {\n id: string;\n screenId: string;\n trace: IntentTraceRef;\n provenance: IntentProvenance;\n confidence: number;\n rule: string;\n targetFieldId?: string;\n ambiguity?: IntentAmbiguity;\n /** Contributing sources (Issue #1431). Optional, additive. */\n sourceRefs?: TestIntentSourceRef[];\n}\n\n/**\n * Navigation edge inferred from prototype links or equivalent.\n *\n * Wave 4 (Issue #1431) adds the optional `sourceRefs` array; see\n * {@link DetectedField} for backward-compat semantics.\n */\nexport interface DetectedNavigation {\n id: string;\n screenId: string;\n trace: IntentTraceRef;\n provenance: IntentProvenance;\n confidence: number;\n targetScreenId: string;\n triggerElementId?: string;\n ambiguity?: IntentAmbiguity;\n /** Contributing sources (Issue #1431). Optional, additive. */\n sourceRefs?: TestIntentSourceRef[];\n}\n\n/**\n * Business-object cluster inferred across one or more fields.\n *\n * Wave 4 (Issue #1431) adds the optional `sourceRefs` array; see\n * {@link DetectedField} for backward-compat semantics.\n */\nexport interface InferredBusinessObject {\n id: string;\n screenId: string;\n trace: IntentTraceRef;\n provenance: IntentProvenance;\n confidence: number;\n name: string;\n fieldIds: string[];\n ambiguity?: IntentAmbiguity;\n /** Contributing sources (Issue #1431). Optional, additive. */\n sourceRefs?: TestIntentSourceRef[];\n}\n\n/**\n * EU-banking locales supported by per-locale Platt-curve calibration.\n *\n * Initial six locales were added in Issue #2117\n * (DE-DE / DE-AT / DE-CH / EN-IE / FR-FR / IT-IT). Issue #2188 extended\n * the corpus with five additional locales (PL-PL, ES-ES, NL-NL, CS-CZ,\n * HU-HU) driven by concrete EU-banking customer pipeline demand.\n *\n * Referenced by `BusinessTestIntentScreen.locale` so that consumers can\n * correlate per-screen locale with the per-locale calibration curves\n * without importing from the test-intelligence submodule.\n */\nexport type SupportedLocale =\n | \"DE-DE\"\n | \"DE-AT\"\n | \"DE-CH\"\n | \"EN-IE\"\n | \"FR-FR\"\n | \"IT-IT\"\n | \"PL-PL\"\n | \"ES-ES\"\n | \"NL-NL\"\n | \"CS-CZ\"\n | \"HU-HU\";\n\n/** Per-screen slice of the intent. */\nexport interface BusinessTestIntentScreen {\n screenId: string;\n screenName: string;\n screenPath?: string;\n trace: IntentTraceRef;\n /**\n * Optional locale tag for this screen (Issue #2117). When present it is\n * one of the six `SupportedLocale` codes; absent for screens whose locale\n * could not be resolved at the import stage. Consumers derive the locale\n * using `deriveLocaleFromBusinessTestIntentScreen` from\n * `locale-calibration.ts` when they need a best-effort value.\n */\n locale?: SupportedLocale;\n}\n\n/**\n * Metadata about the input that produced the IR.\n *\n * Wave 4 (Issue #1431) generalises this single-source descriptor into a\n * discriminated union of seven source kinds carried inside the\n * {@link MultiSourceTestIntentEnvelope}. The legacy `source` field on\n * {@link BusinessTestIntentIr} is kept additive for one minor cycle so\n * single-source Figma jobs that have not opted into the multi-source gate\n * keep producing bit-identical artifacts.\n */\nexport interface BusinessTestIntentIrSource {\n // Standalone extraction (#8): the legacy single-source descriptor admits\n // jira source kinds so the multi-source test corpus type-checks under the\n // standalone product's strict `tsc` over test files. Additive and\n // non-breaking relative to the public contract surface.\n kind:\n | \"figma_local_json\"\n | \"figma_plugin\"\n | \"figma_rest\"\n | \"hybrid\"\n | \"jira_paste\"\n | \"jira_rest\";\n contentHash: string;\n}\n\n/**\n * Redacted, deterministic test-design IR for a job.\n *\n * Wave 4 (Issue #1431) introduces an additive {@link sourceEnvelope} field\n * carrying the multi-source aggregate. The legacy {@link source} singleton\n * is preserved for backward-compat: single-source Figma jobs that have not\n * opted into the multi-source gate keep emitting it as-is.\n */\nexport interface BusinessTestIntentIr {\n version: typeof BUSINESS_TEST_INTENT_IR_SCHEMA_VERSION;\n source: BusinessTestIntentIrSource;\n screens: BusinessTestIntentScreen[];\n detectedFields: DetectedField[];\n detectedActions: DetectedAction[];\n detectedValidations: DetectedValidation[];\n detectedNavigation: DetectedNavigation[];\n inferredBusinessObjects: InferredBusinessObject[];\n risks: string[];\n assumptions: string[];\n openQuestions: string[];\n piiIndicators: PiiIndicator[];\n redactions: IntentRedaction[];\n /**\n * Aggregate envelope of contributing test-design sources (Issue #1431).\n *\n * Optional and additive: omitted for legacy single-source Figma jobs to\n * preserve byte-stable artifacts and replay-cache hits. Populated only\n * when both the parent {@link TEST_INTELLIGENCE_ENV} gate and the\n * {@link TEST_INTELLIGENCE_MULTISOURCE_ENV} gate are enabled, and the\n * parent test-intelligence startup option allows multi-source ingestion.\n */\n sourceEnvelope?: MultiSourceTestIntentEnvelope;\n /**\n * Additive conflict/report payload emitted by the deterministic multi-source\n * reconciliation engine (Issue #1436). Omitted for legacy single-source\n * jobs so existing artifacts remain byte-stable.\n */\n multiSourceConflicts?: MultiSourceConflict[];\n}\n\n/**\n * Compact, versioned projection of `BusinessTestIntentIr` plus optional\n * visual-sidecar evidence. This additive artifact gives downstream prompt\n * compilation a bounded, test-design-oriented surface without replacing the\n * source IR.\n */\nexport interface TestDesignModel {\n schemaVersion: typeof TEST_DESIGN_MODEL_SCHEMA_VERSION;\n jobId: string;\n sourceHash: string;\n screens: TestDesignScreen[];\n /**\n * Structured customer or Jira requirements promoted from bounded source\n * evidence. Optional for backwards-compatible artifact readers; new\n * production runs populate it when a source contains explicit acceptance\n * criteria.\n */\n requirements?: TestDesignRequirement[];\n businessRules: TestDesignRule[];\n calculationConstraints: TestDesignCalculationConstraint[];\n assumptions: TestDesignAssumption[];\n openQuestions: TestDesignOpenQuestion[];\n riskSignals: TestDesignRiskSignal[];\n}\n\nexport interface TestDesignScreen {\n screenId: string;\n name: string;\n purpose?: string;\n elements: TestDesignElement[];\n actions: TestDesignAction[];\n validations: TestDesignValidation[];\n calculations: TestDesignCalculation[];\n visualRefs: string[];\n sourceRefs: string[];\n}\n\nexport interface TestDesignElement {\n elementId: string;\n label: string;\n kind: string;\n defaultValue?: string;\n ambiguity?: string;\n}\n\nexport interface TestDesignAction {\n actionId: string;\n label: string;\n kind: string;\n targetScreenId?: string;\n ambiguity?: string;\n}\n\nexport interface TestDesignValidation {\n validationId: string;\n rule: string;\n targetElementId?: string;\n ambiguity?: string;\n}\n\nexport interface TestDesignCalculation {\n calculationId: string;\n name: string;\n inputElementIds: string[];\n resultElementId?: string;\n ambiguity?: string;\n}\n\nexport interface TestDesignCalculationConstraint {\n constraintId: string;\n kind: \"exclude_component\" | \"include_component\";\n subject: \"financing_need\";\n component: \"vat\";\n evidenceText: string;\n screenId?: string;\n}\n\nexport type TestDesignRequirementKind = \"acceptance_criterion\";\n\nexport type TestDesignRequirementVerificationMode =\n | \"automated\"\n | \"visual\"\n | \"manual_review\";\n\nexport interface TestDesignRequirement {\n requirementId: string;\n kind: TestDesignRequirementKind;\n text: string;\n sourceRefs: string[];\n screenId?: string;\n verificationMode: TestDesignRequirementVerificationMode;\n}\n\nexport interface TestDesignRule {\n ruleId: string;\n description: string;\n screenId?: string;\n sourceRefs: string[];\n}\n\nexport interface TestDesignAssumption {\n assumptionId: string;\n text: string;\n}\n\nexport interface TestDesignOpenQuestion {\n openQuestionId: string;\n text: string;\n}\n\nexport interface TestDesignRiskSignal {\n riskSignalId: string;\n text: string;\n screenId?: string;\n sourceRefs: string[];\n}\n\n/**\n * Source kinds recognised by the multi-source Test Intent ingestion\n * pipeline (Issue #1431). The first three are existing Figma kinds; the\n * remaining four are introduced by Wave 4 issues 4.B–4.E. `custom_markdown`\n * is added by Issue #1441 as a dedicated Markdown supporting source kind.\n */\nexport const ALLOWED_TEST_INTENT_SOURCE_KINDS = [\n \"figma_local_json\",\n \"figma_plugin\",\n \"figma_rest\",\n \"jira_rest\",\n \"jira_paste\",\n \"custom_text\",\n \"custom_structured\",\n \"custom_markdown\",\n] as const;\n\n/** Discriminated source-kind alias derived from {@link ALLOWED_TEST_INTENT_SOURCE_KINDS}. */\nexport type TestIntentSourceKind =\n (typeof ALLOWED_TEST_INTENT_SOURCE_KINDS)[number];\n\n/**\n * Primary source kinds — at least one of these must be present in any\n * envelope. A custom-only envelope must fail validation with\n * `primary_source_required` (Issue #1431, source-mix hardening addendum).\n */\nexport const PRIMARY_TEST_INTENT_SOURCE_KINDS = [\n \"figma_local_json\",\n \"figma_plugin\",\n \"figma_rest\",\n \"jira_rest\",\n \"jira_paste\",\n] as const;\n\n/** Subset alias for primary source kinds. */\nexport type PrimaryTestIntentSourceKind =\n (typeof PRIMARY_TEST_INTENT_SOURCE_KINDS)[number];\n\n/**\n * Supporting (non-primary) source kinds — may only appear alongside at\n * least one primary source. `custom_markdown` is a dedicated Markdown\n * supporting source kind (Issue #1441); it always carries\n * `redactedMarkdownHash` + `plainTextDerivativeHash` and never requires\n * `inputFormat` since its format is intrinsically Markdown.\n */\nexport const SUPPORTING_TEST_INTENT_SOURCE_KINDS = [\n \"custom_text\",\n \"custom_structured\",\n \"custom_markdown\",\n] as const;\n\n/** Subset alias for supporting source kinds. */\nexport type SupportingTestIntentSourceKind =\n (typeof SUPPORTING_TEST_INTENT_SOURCE_KINDS)[number];\n\n/**\n * Conflict-resolution policy discriminant carried on every envelope.\n *\n * - `priority` — apply {@link MultiSourceTestIntentEnvelope.priorityOrder}\n * when sources disagree on a field. The aggregate hash MUST encode the\n * priority order so swapping it forces a cache miss.\n * - `reviewer_decides` — surface conflicts to the reviewer and keep both\n * variants until the reviewer chooses one.\n * - `keep_both` — emit independent test cases per source without merging.\n */\nexport const ALLOWED_CONFLICT_RESOLUTION_POLICIES = [\n \"priority\",\n \"reviewer_decides\",\n \"keep_both\",\n] as const;\n\n/** Conflict-resolution policy alias. */\nexport type ConflictResolutionPolicy =\n (typeof ALLOWED_CONFLICT_RESOLUTION_POLICIES)[number];\n\n/**\n * Recognised input formats for `custom_text` / `custom_structured` sources\n * (Markdown-aware addendum, 2026-04-26). Markdown is treated as\n * user-provided supporting evidence and is NEVER trusted as instructions\n * to the model or runtime.\n */\nexport const ALLOWED_TEST_INTENT_CUSTOM_INPUT_FORMATS = [\n \"plain_text\",\n \"markdown\",\n \"structured_json\",\n] as const;\n\n/** Custom input-format alias. */\nexport type TestIntentCustomInputFormat =\n (typeof ALLOWED_TEST_INTENT_CUSTOM_INPUT_FORMATS)[number];\n\n/**\n * Reference to a single contributing source inside a\n * {@link MultiSourceTestIntentEnvelope}. References are stable per envelope\n * and never carry raw source bytes — only redacted hashes and structured\n * provenance hints.\n */\nexport interface TestIntentSourceRef {\n /** Stable identifier per envelope, e.g. `\"src.0\"`, `\"src.1\"`. */\n sourceId: string;\n /** Discriminated source kind. */\n kind: TestIntentSourceKind;\n /** SHA-256 of the canonicalised source bytes (lowercase hex, 64 chars). */\n contentHash: string;\n /** ISO-8601 UTC capture timestamp (millisecond precision, `Z` suffix). */\n capturedAt: string;\n /**\n * Opaque, non-PII operator handle for paste/custom sources. Never store\n * raw email addresses or full names — callers MUST redact before set.\n */\n authorHandle?: string;\n /**\n * Input format for `custom_text` / `custom_structured`. Required for\n * those kinds, MUST be omitted for primary kinds.\n */\n inputFormat?: TestIntentCustomInputFormat;\n /**\n * Note-entry id for Markdown-authored custom sources (Markdown\n * addendum). Lets provenance references identify the source row in the\n * reviewer note store. Optional, ignored for non-Markdown sources.\n */\n noteEntryId?: string;\n /**\n * Markdown section path (heading / table / list context) for Markdown\n * custom sources, e.g. `\"# Risks > ## PII handling\"`. Optional and\n * ignored for non-Markdown sources.\n */\n markdownSectionPath?: string;\n /**\n * Canonical Jira issue key for `jira_rest` / `jira_paste` sources, e.g.\n * `\"PAY-1234\"`. When both REST and paste sources carry the same key, the\n * validator reports `duplicate_jira_paste_collision` so downstream Wave 4.D\n * routing can resolve the paste collision explicitly.\n */\n canonicalIssueKey?: string;\n /**\n * SHA-256 hash of the canonical redacted Markdown for Markdown-authored\n * custom sources. Raw Markdown is never stored in the envelope.\n */\n redactedMarkdownHash?: string;\n /**\n * SHA-256 hash of the deterministic plain-text derivative produced from the\n * redacted Markdown. This lets prompt-isolation code audit what evidence was\n * made available without treating Markdown as instructions.\n */\n plainTextDerivativeHash?: string;\n}\n\n/** PII-redacted Markdown note persisted as custom supporting context. */\nexport interface CustomContextNoteEntry {\n entryId: string;\n authorHandle: string;\n capturedAt: string;\n inputFormat: \"markdown\";\n /** Canonical allowlist Markdown after PII redaction. */\n bodyMarkdown: string;\n /** Deterministic plain-text derivative of {@link bodyMarkdown}. */\n bodyPlain: string;\n markdownContentHash: string;\n plainContentHash: string;\n piiIndicators: PiiIndicator[];\n redactions: IntentRedaction[];\n}\n\n/** Validated machine-checkable custom supporting attributes. */\nexport interface CustomContextStructuredEntry {\n entryId: string;\n authorHandle: string;\n capturedAt: string;\n attributes: Array<{ key: string; value: string }>;\n contentHash: string;\n piiIndicators: PiiIndicator[];\n redactions: IntentRedaction[];\n}\n\n/** Persisted custom-context source artifact. */\nexport interface CustomContextSource {\n version: typeof CUSTOM_CONTEXT_SCHEMA_VERSION;\n sourceKind: \"custom_text\" | \"custom_structured\";\n noteEntries: CustomContextNoteEntry[];\n structuredEntries: CustomContextStructuredEntry[];\n aggregateContentHash: string;\n}\n\n/** Recognized custom attribute and its intended downstream consumer. */\nexport interface SuggestedCustomContextAttribute {\n key: string;\n label: string;\n downstreamConsumer: string;\n description: string;\n}\n\n/** Curated structured-attribute schema surfaced to API and UI consumers. */\nexport const SUGGESTED_CUSTOM_CONTEXT_ATTRIBUTES: readonly SuggestedCustomContextAttribute[] =\n [\n {\n key: \"regulatory_scope\",\n label: \"regulatoryScope\",\n downstreamConsumer: \"policy_gate.risk_classifier\",\n description: \"Regulatory scope hints such as PSD2 or GDPR.\",\n },\n {\n key: \"test_environment\",\n label: \"testEnvironment\",\n downstreamConsumer: \"prompt_context\",\n description: \"Target execution environment such as preprod-eu.\",\n },\n {\n key: \"data_class\",\n label: \"dataClass\",\n downstreamConsumer: \"policy_gate.risk_classifier\",\n description: \"Sensitive data classification such as PCI-DSS-3.\",\n },\n {\n key: \"priority_hint\",\n label: \"priorityHint\",\n downstreamConsumer: \"prompt_context\",\n description: \"Reviewer priority hint for generated coverage.\",\n },\n {\n key: \"feature_flag\",\n label: \"featureFlag\",\n downstreamConsumer: \"prompt_context.qc_export\",\n description: \"Feature flag context such as NEW_CHECKOUT=on.\",\n },\n {\n key: \"non_functional_profile\",\n label: \"nonFunctionalProfile\",\n downstreamConsumer: \"prompt_context\",\n description: \"Non-functional testing profile such as latency or a11y.\",\n },\n ];\n\n/** Policy signal derived from recognized custom structured attributes. */\nexport interface CustomContextPolicySignal {\n sourceId: string;\n entryId: string;\n attributeKey: string;\n attributeValue: string;\n riskCategory: TestCaseRiskCategory;\n reason: string;\n contentHash: string;\n}\n\n/**\n * Forward reference for source-mix orchestration owned by Issue #1441. Wave\n * 4.A validates the shape only; pipeline routing and reconciliation remain in\n * the downstream source-mix issue.\n */\nexport interface MultiSourceTestIntentSourceMixPlanRef {\n /** Stable hash of the source-mix plan payload owned by Issue #1441. */\n planHash: string;\n /** Ownership marker for downstream orchestration. */\n ownerIssue: \"#1441\";\n}\n\n/**\n * Aggregate envelope of contributing sources (Issue #1431).\n *\n * The envelope is a pure value object; ingestion logic, reconciliation,\n * and orchestration live in downstream Wave 4 issues (4.B / 4.C / 4.D /\n * 4.E / 4.F / 4.H). The envelope guarantees:\n *\n * 1. At least one source.\n * 2. At least one primary source (primary-source-required rule).\n * 3. Stable {@link aggregateContentHash} that is invariant under source\n * reordering when {@link conflictResolutionPolicy} is not `priority`,\n * and changes when source content actually changes.\n * 4. When `conflictResolutionPolicy=\"priority\"`, a non-empty\n * {@link priorityOrder} listing every source kind present in the\n * envelope (no extras).\n */\nexport interface MultiSourceTestIntentEnvelope {\n /** Schema version stamp. */\n version: typeof MULTI_SOURCE_TEST_INTENT_ENVELOPE_SCHEMA_VERSION;\n /** Ordered list of contributing sources (length ≥ 1). */\n sources: TestIntentSourceRef[];\n /**\n * Stable aggregate hash of the contributing sources. Computed via\n * `sha256Hex` of the canonical-sorted (`contentHash`, `kind`) pairs by\n * default, with the `priorityOrder` mixed in when the resolution policy\n * is `priority`.\n */\n aggregateContentHash: string;\n /** Resolution discriminant for cross-source disagreement. */\n conflictResolutionPolicy: ConflictResolutionPolicy;\n /**\n * Required when {@link conflictResolutionPolicy} is `priority`: an\n * ordered, deduplicated list of source kinds covering every kind that\n * appears in {@link sources}. The list participates in the aggregate\n * hash so a different priority order produces a different hash.\n */\n priorityOrder?: TestIntentSourceKind[];\n /** Optional source-mix plan hook owned by Issue #1441. */\n sourceMixPlan?: MultiSourceTestIntentSourceMixPlanRef;\n}\n\n/** Schema version for `multi-source-conflicts.json` (Issue #1436). */\nexport const MULTI_SOURCE_RECONCILIATION_REPORT_SCHEMA_VERSION =\n \"1.0.0\" as const;\n\n/** Canonical filename for the deterministic multi-source conflict artifact. */\nexport const MULTI_SOURCE_CONFLICT_REPORT_ARTIFACT_FILENAME =\n \"multi-source-conflicts.json\" as const;\n\n/** Kinds of cross-source disagreement recognized by Issue #1436. */\nexport type MultiSourceConflictKind =\n | \"field_label_mismatch\"\n | \"validation_rule_mismatch\"\n | \"risk_category_mismatch\"\n | \"test_data_example_mismatch\"\n | \"duplicate_acceptance_criterion\"\n | \"paste_collision\";\n\n/** One deterministic conflict row emitted by the reconciliation engine. */\nexport interface MultiSourceConflict {\n /** SHA-256 of `{ kind, sourceRefs, normalizedValues }`. */\n conflictId: string;\n kind: MultiSourceConflictKind;\n participatingSourceIds: string[];\n /** Sorted, redacted, canonical values that disagreed. */\n normalizedValues: string[];\n resolution:\n | \"auto_priority\"\n | \"deferred_to_reviewer\"\n | \"kept_both\"\n | \"unresolved\";\n /** Stable IR ids affected by this conflict, when known. */\n affectedElementIds?: string[];\n /** Stable screen ids affected by this conflict, when known. */\n affectedScreenIds?: string[];\n /** Optional sanitized detail suitable for reviewer inspection. */\n detail?: string;\n resolvedBy?: string;\n resolvedAt?: string;\n}\n\n/** Stable transcript row describing one merge decision taken by the engine. */\nexport interface MultiSourceReconciliationTranscriptEntry {\n decisionId: string;\n sourceIds: string[];\n action:\n | \"accepted\"\n | \"merged\"\n | \"conflict_recorded\"\n | \"alternative_emitted\"\n | \"source_unmatched\";\n rationale: string;\n affectedElementIds: string[];\n}\n\n/** Aggregate deterministic conflict artifact emitted for a reconciled run. */\nexport interface MultiSourceReconciliationReport {\n version: typeof MULTI_SOURCE_RECONCILIATION_REPORT_SCHEMA_VERSION;\n envelopeHash: string;\n conflicts: MultiSourceConflict[];\n /** Sources that were present but contributed no accepted or conflict rows. */\n unmatchedSources: string[];\n /**\n * Stable conceptual-case mapping used by downstream reviewers. Each id is a\n * deterministic synthetic case key produced by the reconciliation engine.\n */\n contributingSourcesPerCase: Array<{\n testCaseId: string;\n sourceIds: string[];\n }>;\n policyApplied: ConflictResolutionPolicy;\n transcript: MultiSourceReconciliationTranscriptEntry[];\n}\n\n/**\n * Refusal codes emitted by the multi-source envelope validator\n * (Issue #1431). Stable, locale-independent strings safe to ship to\n * automation.\n */\nexport const ALLOWED_MULTI_SOURCE_ENVELOPE_REFUSAL_CODES = [\n \"envelope_missing\",\n \"envelope_version_mismatch\",\n \"sources_empty\",\n \"duplicate_source_id\",\n \"invalid_source_id\",\n \"invalid_source_kind\",\n \"invalid_content_hash\",\n \"invalid_captured_at\",\n \"invalid_author_handle\",\n \"primary_source_required\",\n \"duplicate_jira_paste_collision\",\n \"custom_input_format_required\",\n \"custom_input_format_invalid\",\n \"primary_source_input_format_invalid\",\n \"markdown_metadata_only_for_custom\",\n \"markdown_hash_required\",\n \"markdown_hash_only_for_markdown\",\n \"jira_issue_key_invalid\",\n \"jira_issue_key_only_for_jira\",\n \"invalid_conflict_resolution_policy\",\n \"priority_order_required\",\n \"priority_order_invalid_kind\",\n \"priority_order_incomplete\",\n \"priority_order_duplicate\",\n \"aggregate_hash_mismatch\",\n \"source_mix_plan_invalid\",\n] as const;\n\n/** Refusal code alias for the multi-source envelope validator. */\nexport type MultiSourceEnvelopeRefusalCode =\n (typeof ALLOWED_MULTI_SOURCE_ENVELOPE_REFUSAL_CODES)[number];\n\n/**\n * A single validation issue surfaced by the multi-source envelope\n * validator. `path` is a JS property-path-like locator (e.g.\n * `\"sources[2].contentHash\"`).\n */\nexport interface MultiSourceEnvelopeIssue {\n code: MultiSourceEnvelopeRefusalCode;\n path?: string;\n detail?: string;\n}\n\n/**\n * Result of multi-source envelope validation (Issue #1431). Hand-rolled\n * to keep the package free of external schema libraries.\n */\nexport type MultiSourceEnvelopeValidationResult =\n | { ok: true; envelope: MultiSourceTestIntentEnvelope }\n | { ok: false; issues: MultiSourceEnvelopeIssue[] };\n\n/**\n * Refusal codes for the multi-source mode gate (Issue #1431). The gate\n * enforces three nested invariants before any multi-source ingestion is\n * permitted:\n *\n * 1. Parent test-intelligence env + startup option enabled.\n * 2. Multi-source env + startup option enabled.\n * 3. `llmCodegenMode === \"deterministic\"`.\n *\n * Any failed check fails closed with zero side effects and surfaces a\n * structured diagnostic.\n */\nexport const ALLOWED_MULTI_SOURCE_MODE_GATE_REFUSAL_CODES = [\n \"test_intelligence_disabled\",\n \"multi_source_env_disabled\",\n \"multi_source_startup_option_disabled\",\n \"llm_codegen_mode_locked\",\n] as const;\n\n/** Refusal-code alias for the multi-source mode gate. */\nexport type MultiSourceModeGateRefusalCode =\n (typeof ALLOWED_MULTI_SOURCE_MODE_GATE_REFUSAL_CODES)[number];\n\n/** Inputs accepted by `evaluateMultiSourceModeGate`. */\nexport interface MultiSourceModeGateInput {\n testIntelligenceEnvEnabled: boolean;\n testIntelligenceStartupEnabled: boolean;\n multiSourceEnvEnabled: boolean;\n multiSourceStartupEnabled: boolean;\n llmCodegenMode?: string;\n}\n\n/** Single refusal entry on a {@link MultiSourceModeGateDecision}. */\nexport interface MultiSourceModeGateRefusal {\n code: MultiSourceModeGateRefusalCode;\n detail: string;\n}\n\n/** Decision produced by `evaluateMultiSourceModeGate`. */\nexport interface MultiSourceModeGateDecision {\n allowed: boolean;\n refusals: MultiSourceModeGateRefusal[];\n}\n\n/**\n * Jira issue intermediate representation (Issue #1432, Wave 4.B).\n *\n * The Jira IR is the canonical, PII-redacted, deterministically-hashed\n * surface produced from raw Jira issue payloads — independent of whether\n * the payload arrived via REST (Wave 4.C) or copy-paste (Wave 4.D). Wave\n * 4.F's reconciliation engine and the LLM prompt compiler consume only\n * this IR; raw Jira payloads, raw ADF rich-text, attachment bytes, user\n * account IDs, internal hostnames, and Jira `self`/avatar/download URLs\n * MUST never reach a persisted artifact or a model prompt.\n *\n * The IR is data-minimized by default: comments, attachments, linked\n * issues, and unknown custom fields are excluded unless the caller\n * explicitly opts each field group in. Every inclusion / exclusion /\n * cap / redaction decision is recorded in {@link JiraIssueIrDataMinimization}\n * so audits can prove what was collected and why.\n */\n\n/** Schema version stamp for the {@link JiraIssueIr} artifact. */\nexport const JIRA_ISSUE_IR_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Run-dir-relative subdirectory under which per-source Jira IR artifacts\n * are persisted, namespaced by {@link TestIntentSourceRef.sourceId}.\n *\n * Layout: `<runDir>/sources/<sourceId>/jira-issue-ir.json`.\n */\nexport const JIRA_ISSUE_IR_ARTIFACT_DIRECTORY = \"sources\" as const;\n\n/** Canonical filename for the persisted Jira IR artifact. */\nexport const JIRA_ISSUE_IR_ARTIFACT_FILENAME = \"jira-issue-ir.json\" as const;\n\n/**\n * Hard pre-parse byte cap on the serialized ADF JSON document. Inputs\n * exceeding this are rejected with `jira_adf_payload_too_large` before\n * any tree traversal — the parser MUST NOT allocate proportional to the\n * payload above this bound.\n */\nexport const MAX_JIRA_ADF_INPUT_BYTES = 1_048_576 as const;\n\n/**\n * Hard cap on the UTF-8 byte length of {@link JiraIssueIr.descriptionPlain}\n * after ADF normalization + PII redaction. Over-cap descriptions are\n * truncated and the truncation is recorded in {@link JiraIssueIrDataMinimization.descriptionTruncated}.\n */\nexport const MAX_JIRA_DESCRIPTION_PLAIN_BYTES = 32_768 as const;\n\n/**\n * Hard cap on the UTF-8 byte length of any single normalized + redacted\n * Jira comment body. Over-cap comments are truncated and counted in\n * {@link JiraIssueIrDataMinimization.commentsCapped}.\n */\nexport const MAX_JIRA_COMMENT_BODY_BYTES = 4_096 as const;\n\n/**\n * Hard cap on the number of Jira comments persisted in a single IR.\n * Over-cap comments are dropped and counted in\n * {@link JiraIssueIrDataMinimization.commentsDropped}.\n */\nexport const MAX_JIRA_COMMENT_COUNT = 50 as const;\n\n/**\n * Hard cap on the number of Jira attachments persisted in a single IR.\n * Attachment bytes are NEVER persisted — only metadata.\n */\nexport const MAX_JIRA_ATTACHMENT_COUNT = 50 as const;\n\n/** Hard cap on the number of Jira linked-issue refs persisted in a single IR. */\nexport const MAX_JIRA_LINK_COUNT = 50 as const;\n\n/** Hard cap on the number of custom fields persisted in a single IR. */\nexport const MAX_JIRA_CUSTOM_FIELD_COUNT = 50 as const;\n\n/** Hard cap on the UTF-8 byte length of a single normalized + redacted custom-field value. */\nexport const MAX_JIRA_CUSTOM_FIELD_VALUE_BYTES = 2_048 as const;\n\n/**\n * Allow-listed Jira issue type discriminants. Anything outside this set\n * collapses to `\"other\"` so the IR cannot be tricked into smuggling a\n * free-form issue-type string into the prompt or downstream prompts.\n */\nexport const ALLOWED_JIRA_ISSUE_TYPES = [\n \"story\",\n \"task\",\n \"bug\",\n \"epic\",\n \"subtask\",\n \"other\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_JIRA_ISSUE_TYPES}. */\nexport type JiraIssueType = (typeof ALLOWED_JIRA_ISSUE_TYPES)[number];\n\n/**\n * Allow-listed Atlassian Document Format node `type` discriminants. The\n * parser fails closed on any node whose `type` is not in this set\n * (`jira_adf_unknown_node_type`).\n */\nexport const ALLOWED_JIRA_ADF_NODE_TYPES = [\n \"doc\",\n \"paragraph\",\n \"heading\",\n \"blockquote\",\n \"bulletList\",\n \"orderedList\",\n \"listItem\",\n \"codeBlock\",\n \"rule\",\n \"panel\",\n \"table\",\n \"tableRow\",\n \"tableHeader\",\n \"tableCell\",\n \"mediaSingle\",\n \"mediaGroup\",\n \"media\",\n \"text\",\n \"hardBreak\",\n \"mention\",\n \"emoji\",\n \"inlineCard\",\n \"status\",\n \"date\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_JIRA_ADF_NODE_TYPES}. */\nexport type JiraAdfNodeType = (typeof ALLOWED_JIRA_ADF_NODE_TYPES)[number];\n\n/**\n * Allow-listed Atlassian Document Format `mark.type` discriminants. Marks\n * carry inline annotation (e.g. `strong`, `link`) on `text` nodes. Marks\n * outside this set are rejected with `jira_adf_unknown_mark_type`.\n */\nexport const ALLOWED_JIRA_ADF_MARK_TYPES = [\n \"strong\",\n \"em\",\n \"code\",\n \"strike\",\n \"underline\",\n \"link\",\n \"subsup\",\n \"textColor\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_JIRA_ADF_MARK_TYPES}. */\nexport type JiraAdfMarkType = (typeof ALLOWED_JIRA_ADF_MARK_TYPES)[number];\n\n/**\n * Refusal codes emitted by the ADF parser (`parseJiraAdfDocument`). The\n * parser never throws — it returns a discriminated union and these codes\n * are stable, locale-independent strings safe to ship to automation.\n */\nexport const ALLOWED_JIRA_ADF_REJECTION_CODES = [\n \"jira_adf_payload_too_large\",\n \"jira_adf_input_not_string\",\n \"jira_adf_input_not_json\",\n \"jira_adf_root_not_object\",\n \"jira_adf_root_type_invalid\",\n \"jira_adf_unknown_node_type\",\n \"jira_adf_unknown_mark_type\",\n \"jira_adf_node_shape_invalid\",\n \"jira_adf_text_node_invalid\",\n \"jira_adf_max_depth_exceeded\",\n \"jira_adf_max_node_count_exceeded\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_JIRA_ADF_REJECTION_CODES}. */\nexport type JiraAdfRejectionCode =\n (typeof ALLOWED_JIRA_ADF_REJECTION_CODES)[number];\n\n/**\n * Refusal codes emitted by the Jira IR builder (`buildJiraIssueIr`) and\n * by the Jira-issue-key / JQL-fragment validators. Stable and\n * locale-independent.\n */\nexport const ALLOWED_JIRA_IR_REFUSAL_CODES = [\n \"jira_issue_key_invalid\",\n \"jira_issue_key_too_long\",\n \"jira_issue_type_invalid\",\n \"jira_summary_invalid\",\n \"jira_description_invalid\",\n \"jira_acceptance_criterion_invalid\",\n \"jira_comment_invalid\",\n \"jira_attachment_invalid\",\n \"jira_link_invalid\",\n \"jira_custom_field_invalid\",\n \"jira_custom_field_id_invalid\",\n \"jira_status_invalid\",\n \"jira_priority_invalid\",\n \"jira_field_selection_profile_invalid\",\n \"jira_captured_at_invalid\",\n \"jira_field_unknown_excluded\",\n \"jira_jql_fragment_disallowed_token\",\n \"jira_jql_fragment_control_character\",\n \"jira_jql_fragment_too_long\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_JIRA_IR_REFUSAL_CODES}. */\nexport type JiraIrRefusalCode = (typeof ALLOWED_JIRA_IR_REFUSAL_CODES)[number];\n\n/** Single normalized acceptance criterion derived from a Jira issue. */\nexport interface JiraAcceptanceCriterion {\n /** Stable per-issue id, e.g. `\"ac.0\"`, `\"ac.1\"`. */\n id: string;\n /** Plain-text criterion body, PII-redacted. */\n text: string;\n /** Original Jira field id this criterion was sourced from (e.g. `\"customfield_10042\"`). */\n sourceFieldId?: string;\n}\n\n/** Single PII-redacted Jira comment carried into the IR (opt-in only). */\nexport interface JiraComment {\n /** Stable per-issue id, e.g. `\"comment.0\"`. */\n id: string;\n /**\n * Opaque non-PII author handle (never raw email, full name, or Jira\n * accountId). Resolution is the caller's responsibility before the\n * comment reaches the builder.\n */\n authorHandle?: string;\n /** ISO-8601 UTC timestamp of the original Jira comment. */\n createdAt: string;\n /** PII-redacted comment body. May be truncated to the configured byte cap. */\n body: string;\n /** True when the body was truncated to fit {@link MAX_JIRA_COMMENT_BODY_BYTES}. */\n bodyTruncated: boolean;\n}\n\n/**\n * Metadata reference to a Jira attachment. The IR NEVER carries\n * attachment bytes — only the redacted filename, MIME type, and byte\n * size. Download URLs are stripped by the builder.\n */\nexport interface JiraAttachmentRef {\n /** Stable per-issue id, e.g. `\"attachment.0\"`. */\n id: string;\n /** PII-redacted attachment filename. */\n filename: string;\n /** Reported MIME type, normalised to lowercase. */\n mimeType?: string;\n /** Reported byte size, if known. */\n byteSize?: number;\n}\n\n/** Reference to another Jira issue linked from this one (opt-in). */\nexport interface JiraLinkRef {\n /** Stable per-issue id, e.g. `\"link.0\"`. */\n id: string;\n /** Validated Jira issue key of the linked issue. */\n targetIssueKey: string;\n /** Normalized link relationship label (e.g. `\"blocks\"`, `\"relates_to\"`). */\n relationship: string;\n}\n\n/** Single PII-redacted custom field included in the IR (opt-in only). */\nexport interface JiraIssueIrCustomField {\n /** Jira custom-field id (e.g. `\"customfield_10042\"`). */\n id: string;\n /** PII-redacted custom-field display name. */\n nameRedacted: string;\n /** PII-redacted, byte-capped, normalized scalar value. */\n valuePlain: string;\n /** True when the value was truncated to fit {@link MAX_JIRA_CUSTOM_FIELD_VALUE_BYTES}. */\n valueTruncated: boolean;\n}\n\n/**\n * Field-selection profile applied by the Jira IR builder. The default is\n * data-minimized: comments, attachments, linked issues, and custom fields\n * are excluded unless the caller opts each group in. Unknown custom-field\n * ids are always excluded — there is no opt-in path for \"all custom\n * fields\".\n */\nexport interface JiraFieldSelectionProfile {\n /** Include the description body (default `true`). */\n includeDescription: boolean;\n /** Include comments (default `false`). */\n includeComments: boolean;\n /** Include attachment metadata (default `false`). */\n includeAttachments: boolean;\n /** Include linked-issue refs (default `false`). */\n includeLinks: boolean;\n /**\n * Allow-list of Jira custom-field ids whose values are persisted on\n * the IR. Anything outside this list is excluded and counted in\n * {@link JiraIssueIrDataMinimization.unknownCustomFieldsExcluded}.\n */\n customFieldAllowList: readonly string[];\n /**\n * Allow-list of Jira custom-field ids interpreted as acceptance\n * criteria. The builder reads these fields, parses them as ADF when\n * appropriate, and emits {@link JiraAcceptanceCriterion} entries.\n */\n acceptanceCriterionFieldIds: readonly string[];\n}\n\n/**\n * Default Jira field selection profile — data-minimized by default. No\n * comments, no attachments, no linked issues, no unknown custom fields.\n * Description is included; acceptance criteria require explicit\n * configuration.\n */\nexport const DEFAULT_JIRA_FIELD_SELECTION_PROFILE: JiraFieldSelectionProfile =\n Object.freeze({\n includeDescription: true,\n includeComments: false,\n includeAttachments: false,\n includeLinks: false,\n customFieldAllowList: Object.freeze([]) as readonly string[],\n acceptanceCriterionFieldIds: Object.freeze([]) as readonly string[],\n });\n\n/**\n * Audit metadata recording how the data-minimization profile was applied\n * to a single IR build. Lets reviewers verify that opt-in field groups\n * were turned on intentionally, that over-large bodies were capped before\n * persistence, and that unknown custom fields were excluded by default.\n */\nexport interface JiraIssueIrDataMinimization {\n /** True when the description body was included on the IR. */\n descriptionIncluded: boolean;\n /** True when the description body was truncated to fit the byte cap. */\n descriptionTruncated: boolean;\n /** True when comments were included on the IR (opt-in). */\n commentsIncluded: boolean;\n /** Count of comments dropped because the count cap was exceeded. */\n commentsDropped: number;\n /** Count of comments whose body was truncated to fit the byte cap. */\n commentsCapped: number;\n /** True when attachment metadata was included on the IR (opt-in). */\n attachmentsIncluded: boolean;\n /** Count of attachments dropped because the count cap was exceeded. */\n attachmentsDropped: number;\n /** True when linked-issue refs were included on the IR (opt-in). */\n linksIncluded: boolean;\n /** Count of links dropped because the count cap was exceeded. */\n linksDropped: number;\n /** Count of custom fields included via the explicit allow-list. */\n customFieldsIncluded: number;\n /** Count of custom fields excluded because they were not on the allow-list. */\n unknownCustomFieldsExcluded: number;\n /** Count of custom-field values truncated to fit the per-field byte cap. */\n customFieldsCapped: number;\n}\n\n/**\n * Canonical, PII-redacted, deterministically-hashed Jira issue IR. Wave\n * 4.F's reconciliation engine and the LLM prompt compiler consume only\n * this IR — raw Jira payloads never reach prompt compilation.\n *\n * Hard invariants enforced by the builder:\n *\n * 1. `issueKey` is validated (`^[A-Z][A-Z0-9_]+-[1-9][0-9]*$`, ≤ 64 chars).\n * 2. `descriptionPlain`, `summary`, comment bodies, custom-field values,\n * attachment filenames, and link relationships are all PII-redacted\n * before persistence.\n * 3. No Jira `self` URL, account id, avatar URL, attachment download\n * URL, or raw `names`/`schema` map is present anywhere on the IR.\n * 4. `contentHash` is the SHA-256 of the canonical JSON serialization\n * of the IR with `contentHash` itself stripped.\n * 5. Audit/data-minimization metadata is always present.\n */\nexport interface JiraIssueIr {\n /** Schema version stamp. */\n version: typeof JIRA_ISSUE_IR_SCHEMA_VERSION;\n /** Validated Jira issue key, e.g. `\"PAY-1234\"`. */\n issueKey: string;\n /** Allow-listed issue type discriminant. Free-form types collapse to `\"other\"`. */\n issueType: JiraIssueType;\n /** PII-redacted summary line. */\n summary: string;\n /** PII-redacted plain-text description, capped at {@link MAX_JIRA_DESCRIPTION_PLAIN_BYTES}. */\n descriptionPlain: string;\n /** Acceptance criteria parsed from explicitly configured custom fields. */\n acceptanceCriteria: JiraAcceptanceCriterion[];\n /** Sorted, deduplicated, PII-redacted labels. */\n labels: string[];\n /** Sorted, deduplicated, PII-redacted component names. */\n components: string[];\n /** Sorted, deduplicated fix-version names. */\n fixVersions: string[];\n /** Issue status name (e.g. `\"In Progress\"`). */\n status: string;\n /** Optional priority name (e.g. `\"High\"`). */\n priority?: string;\n /** Allow-listed custom fields with PII-redacted values (opt-in). */\n customFields: JiraIssueIrCustomField[];\n /** PII-redacted comments (opt-in only). */\n comments: JiraComment[];\n /** Attachment metadata only — NEVER bytes (opt-in only). */\n attachments: JiraAttachmentRef[];\n /** Linked-issue refs (opt-in only). */\n links: JiraLinkRef[];\n /** PII indicators surfaced during redaction. */\n piiIndicators: PiiIndicator[];\n /** Redaction records corresponding to {@link piiIndicators}. */\n redactions: IntentRedaction[];\n /** Data-minimization audit metadata. */\n dataMinimization: JiraIssueIrDataMinimization;\n /** ISO-8601 UTC timestamp at which the IR was built (`Z` suffix). */\n capturedAt: string;\n /** SHA-256 of the canonical IR with `contentHash` stripped. Lowercase, 64 hex. */\n contentHash: string;\n}\n\n/** Visual-sidecar description produced by a multimodal vision model (Issue #1386). */\nexport interface VisualScreenDescription {\n screenId: string;\n sidecarDeployment: SidecarDeployment;\n regions: Array<{\n regionId: string;\n confidence: number;\n label?: string;\n controlType?: string;\n visibleText?: string;\n stateHints?: string[];\n validationHints?: string[];\n ambiguity?: IntentAmbiguity;\n }>;\n confidenceSummary: { min: number; max: number; mean: number };\n screenName?: string;\n capturedAt?: string;\n piiFlags?: Array<{\n regionId: string;\n kind: PiiKind;\n confidence: number;\n }>;\n}\n\n/**\n * Generated test case surface (Issue #1362).\n *\n * The generator-side artifacts described below model the JSON the LLM is\n * asked to produce, the redacted compiled prompt request that is persisted\n * in evidence, and the replay-cache key used to short-circuit identical\n * jobs without ever reaching the gateway.\n */\n\n/** ISO/IEC/IEEE 29119-4 technique tags supported by the generator. */\nexport type TestCaseTechnique29119 =\n | \"equivalence_partitioning\"\n | \"boundary_value_analysis\"\n | \"decision_table\"\n | \"state_transition\"\n | \"use_case\"\n | \"exploratory\"\n | \"error_guessing\"\n | \"syntax_testing\"\n | \"classification_tree\";\n\n/** Coarse-grain test level. */\nexport type TestCaseLevel =\n | \"unit\"\n | \"component\"\n | \"integration\"\n | \"system\"\n | \"acceptance\";\n\n/** Coarse-grain test type. */\nexport type TestCaseType =\n | \"functional\"\n | \"negative\"\n | \"boundary\"\n | \"validation\"\n | \"navigation\"\n | \"regression\"\n | \"exploratory\"\n | \"accessibility\";\n\n/** Risk band attached to a generated test case. */\nexport type TestCaseRiskCategory =\n | \"low\"\n | \"medium\"\n | \"high\"\n | \"regulated_data\"\n | \"financial_transaction\";\n\n/** Priority band attached to a generated test case. */\nexport type TestCasePriority = \"p0\" | \"p1\" | \"p2\" | \"p3\";\n\n/** Persisted polarity label consumed by downstream exports and evals. */\nexport type GeneratedTestCasePolarity =\n (typeof ALLOWED_GENERATED_TEST_CASE_POLARITIES)[number];\n\n/** Persisted customer-eval rubric category for downstream consumers. */\nexport type GeneratedTestCaseCategory =\n (typeof ALLOWED_GENERATED_TEST_CASE_CATEGORIES)[number];\n\n/** Review state at the moment the test case is emitted. */\nexport type GeneratedTestCaseReviewState =\n | \"draft\"\n | \"auto_approved\"\n | \"needs_review\"\n | \"rejected\";\n\n/** Single ordered step inside a generated test case. */\nexport interface GeneratedTestCaseStep {\n index: number;\n action: string;\n data?: string;\n expected?: string;\n /**\n * Stable workflow-topology field lifecycle transition id exercised by this\n * step (Issue #2072). Every step must anchor to one deterministic\n * field-level transition when a workflow topology with field lifecycles is\n * available.\n */\n fieldLifecycleTransitionId?: string;\n}\n\n/** Reference back to a Figma trace path that motivated a test case. */\nexport interface GeneratedTestCaseFigmaTrace {\n screenId: string;\n nodeId?: string;\n nodeName?: string;\n nodePath?: string;\n}\n\n/** QC/ALM mapping preview emitted alongside the test case. */\nexport interface GeneratedTestCaseQcMapping {\n /** Canonical test-case folder hint inside QC/ALM. */\n folderHint?: string;\n /** Canonical mapping profile id this preview was rendered for. */\n mappingProfileId?: string;\n /**\n * Optional hardening stamp clarifying that `exportable` is only a\n * mapping-preview signal and NOT the final policy/review export decision.\n */\n decisionBasis?: \"mapping_preview_only\";\n /** Whether the case is exportable as-is under the mapping profile. */\n exportable: boolean;\n /** Human-readable reasons when exportable=false. */\n blockingReasons?: string[];\n}\n\n/** Quality signal fields attached to each generated test case. */\nexport interface GeneratedTestCaseQualitySignals {\n coveredFieldIds: string[];\n coveredActionIds: string[];\n coveredValidationIds: string[];\n coveredNavigationIds: string[];\n /**\n * Optional customer/Jira requirement ids covered by this case, for example\n * `AC-001`. Older artifacts omit it; coverage consumers treat omission as\n * an empty list.\n */\n coveredRequirementIds?: string[];\n /** 0..1 — generator-side confidence in the produced case. */\n confidence: number;\n /** Optional ambiguity note. */\n ambiguity?: IntentAmbiguity;\n}\n\n/** Auditable raw inputs that feed the per-case confidence calibration. */\nexport interface GeneratedTestCaseConfidenceComponents {\n /** Cross-judge agreement proxy in [0, 1]. */\n judgePanelAgreement: number;\n /** Per-case faithfulness score in [0, 1]. */\n faithfulnessScore: number;\n /** Per-case self-consistency agreement in [0, 1]. */\n selfConsistencyAgreement: number;\n /** Historical anchor strength in [0, 1]. */\n ragHitStrength: number;\n /** Whether deterministic test-data oracle evidence resolved at least one field. */\n oracleResolved: boolean;\n /** Weighted pre-calibration raw score in [0, 1]. */\n rawScore: number;\n}\n\n/** Schema version for persisted `workflow-topology.json` artifacts. */\nexport const WORKFLOW_TOPOLOGY_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the deterministic workflow-topology artifact. */\nexport const WORKFLOW_TOPOLOGY_ARTIFACT_FILENAME =\n \"workflow-topology.json\" as const;\n\n/** Allowed per-field lifecycle states emitted in workflow topology. */\nexport const ALLOWED_WORKFLOW_FIELD_LIFECYCLE_STATES = [\n \"initial\",\n \"focused\",\n \"in_progress\",\n \"validated\",\n \"error\",\n \"terminal\",\n] as const;\nexport type WorkflowFieldLifecycleState =\n (typeof ALLOWED_WORKFLOW_FIELD_LIFECYCLE_STATES)[number];\n\n/** Allowed per-field lifecycle triggers emitted in workflow topology. */\nexport const ALLOWED_WORKFLOW_FIELD_LIFECYCLE_TRIGGERS = [\n \"user_focus\",\n \"user_input\",\n \"validation_pass\",\n \"validation_fail\",\n \"form_commit\",\n] as const;\nexport type WorkflowFieldLifecycleTrigger =\n (typeof ALLOWED_WORKFLOW_FIELD_LIFECYCLE_TRIGGERS)[number];\n\n/** One stable workflow action emitted by the action-topology agent. */\nexport interface WorkflowTopologyAction {\n readonly actionId: string;\n readonly screenId: string;\n readonly label: string;\n readonly kind:\n | \"enter_value\"\n | \"select_option\"\n | \"review_result\"\n | \"review_copy\"\n | \"confirm_state\";\n readonly targetIds: readonly string[];\n readonly sourceRefs: readonly string[];\n}\n\n/** One stable workflow state emitted by the action-topology agent. */\nexport interface WorkflowTopologyState {\n readonly stateId: string;\n readonly screenId: string;\n readonly label: string;\n readonly sourceRefs: readonly string[];\n}\n\n/** One workflow transition between stable states. */\nexport interface WorkflowTopologyTransition {\n readonly transitionId: string;\n readonly from: string;\n readonly to: string;\n readonly guard: string;\n readonly actions: readonly string[];\n}\n\n/** One per-field lifecycle transition emitted by the action-topology agent. */\nexport interface WorkflowFieldLifecycleTransition {\n readonly transitionId: string;\n readonly from: WorkflowFieldLifecycleState;\n readonly to: WorkflowFieldLifecycleState;\n readonly trigger: WorkflowFieldLifecycleTrigger;\n}\n\n/** One stable per-field lifecycle emitted by the action-topology agent. */\nexport interface WorkflowFieldLifecycle {\n readonly fieldId: string;\n readonly states: readonly WorkflowFieldLifecycleState[];\n readonly transitions: readonly WorkflowFieldLifecycleTransition[];\n}\n\n/** Deterministic workflow topology derived from the test-design model. */\nexport interface WorkflowTopology {\n readonly schemaVersion: typeof WORKFLOW_TOPOLOGY_SCHEMA_VERSION;\n readonly jobId: string;\n readonly actions: readonly WorkflowTopologyAction[];\n readonly states: readonly WorkflowTopologyState[];\n readonly transitions: readonly WorkflowTopologyTransition[];\n readonly fieldLifecycles: readonly WorkflowFieldLifecycle[];\n readonly entryStates: readonly string[];\n readonly exitStates: readonly string[];\n}\n\n/**\n * Per-test-case rubric quality signal emitted by the self-verify pass\n * (Issue #1379). The signal is reported via the `self-verify-rubric.json`\n * artifact rather than mutated onto the cached `GeneratedTestCase` so\n * the strict generated-test-case JSON schema and the replay-cache\n * identity remain byte-stable. Each row mirrors one\n * `SelfVerifyRubricCaseEvaluation` from the rubric report and is\n * surfaced on the inspector + the audit-timeline as a quality signal of\n * the underlying test case.\n */\nexport interface TestCaseQualitySignalRubric {\n testCaseId: string;\n /** 0..1 aggregate rubric score for this case (rounded to 6 digits). */\n rubricScore: number;\n}\n\n/**\n * Regulatory-domain enum for {@link RegulatoryRelevance.domain}\n * (Issue #1735, contract bump 4.27.0).\n *\n * Drives the banking/insurance prompt-augmentation pass in the production\n * runner. The enum is intentionally narrow — generic-compliance language\n * only (no specific paragraph numbers / regulatory-text citations).\n *\n * - `\"banking\"` — semantic banking node names (\"Antrag\", \"Auszahlung\",\n * \"Bonität\", IBAN/BIC inputs, four-eyes-state-changing actions, ...).\n * - `\"insurance\"` — semantic insurance node names (\"Versicherung\", \"Police\",\n * \"Schadensfall\", \"Risikoprüfung\", ...).\n * - `\"general\"` — flagged as compliance-relevant but not specific to the\n * above two industries (e.g. PII boundary cases).\n */\nexport const ALLOWED_REGULATORY_RELEVANCE_DOMAINS = [\n \"banking\",\n \"insurance\",\n \"general\",\n] as const;\nexport type RegulatoryRelevanceDomain =\n (typeof ALLOWED_REGULATORY_RELEVANCE_DOMAINS)[number];\n\n/**\n * Banking / insurance semantic keywords surfaced in screen / node names that\n * trigger the regulatory-prompt augmentation pass. The list is intentionally\n * narrow: generic banking + insurance flow vocabulary (German), no specific\n * regulatory-text citations.\n *\n * Exposed as a frozen contract export so callers (production runner +\n * inspector tooling) share one source of truth for \"is this screen regulated\".\n */\nexport const BANKING_INSURANCE_SEMANTIC_KEYWORDS = [\n \"Versicherung\",\n \"Police\",\n \"Schadensfall\",\n \"Risikoprüfung\",\n \"Bonität\",\n \"Antrag\",\n \"Abschluss\",\n \"Auszahlung\",\n \"Kündigung\",\n] as const;\nexport type BankingInsuranceSemanticKeyword =\n (typeof BANKING_INSURANCE_SEMANTIC_KEYWORDS)[number];\n\n/**\n * Optional per-test-case regulatory-relevance signal (Issue #1735, contract\n * bump 4.27.0). Populated by the production runner when the source screen\n * matches a banking / insurance semantic keyword (see\n * {@link BANKING_INSURANCE_SEMANTIC_KEYWORDS}) or when the prompt-augmentation\n * pass produced a compliance-flavoured case (PII / IBAN rejection,\n * four-eyes / audit-trail, regulated-data boundary).\n *\n * The field is optional — non-banking/insurance Figma sources do not emit\n * it, which means existing artifacts and replay-cache entries from contract\n * version 4.26.0 remain valid (additive, backwards-compatible field).\n */\nexport interface RegulatoryRelevance {\n domain: RegulatoryRelevanceDomain;\n /**\n * Free-form German rationale (≤ 240 chars) explaining why the case carries\n * regulatory weight. Generic compliance language only; the prompt\n * augmentation forbids the model from citing specific paragraphs.\n */\n rationale: string;\n}\n\n/** Audit metadata attached to a generated test case. */\nexport interface GeneratedTestCaseAuditMetadata {\n jobId: string;\n generatedAt: string;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n schemaVersion: typeof GENERATED_TEST_CASE_SCHEMA_VERSION;\n promptTemplateVersion: typeof TEST_INTELLIGENCE_PROMPT_TEMPLATE_VERSION;\n redactionPolicyVersion: typeof REDACTION_POLICY_VERSION;\n visualSidecarSchemaVersion: typeof VISUAL_SIDECAR_SCHEMA_VERSION;\n /** Whether the artifact came from a replay-cache hit. */\n cacheHit: boolean;\n cacheKey: string;\n inputHash: string;\n promptHash: string;\n schemaHash: string;\n /** Number of upstream repair instructions clipped before regeneration. */\n truncatedInstructionCount?: number;\n}\n\n/** Single generated test case. */\nexport interface GeneratedTestCase {\n id: string;\n sourceJobId: string;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n schemaVersion: typeof GENERATED_TEST_CASE_SCHEMA_VERSION;\n promptTemplateVersion: typeof TEST_INTELLIGENCE_PROMPT_TEMPLATE_VERSION;\n title: string;\n objective: string;\n level: TestCaseLevel;\n type: TestCaseType;\n /**\n * Optional additive Issue #2030 field. New emissions always populate it;\n * older artifacts may omit it and are classified on read-path fallback.\n */\n polarity?: GeneratedTestCasePolarity;\n /**\n * Optional additive Issue #2030 field. New emissions always populate it;\n * older artifacts may omit it and are classified on read-path fallback.\n */\n category?: GeneratedTestCaseCategory;\n priority: TestCasePriority;\n riskCategory: TestCaseRiskCategory;\n technique: TestCaseTechnique29119;\n preconditions: string[];\n testData: string[];\n steps: GeneratedTestCaseStep[];\n expectedResults: string[];\n figmaTraceRefs: GeneratedTestCaseFigmaTrace[];\n assumptions: string[];\n openQuestions: string[];\n qcMappingPreview: GeneratedTestCaseQcMapping;\n qualitySignals: GeneratedTestCaseQualitySignals;\n /** Optional calibrated per-case acceptance probability (Issue #2074). */\n confidence?: number;\n /** Optional auditable raw inputs used to derive `confidence`. */\n confidenceComponents?: GeneratedTestCaseConfidenceComponents;\n reviewState: GeneratedTestCaseReviewState;\n audit: GeneratedTestCaseAuditMetadata;\n /**\n * Optional regulatory-relevance signal (Issue #1735, contract bump\n * 4.27.0). Populated by the production runner when the source screen\n * matches banking/insurance semantic keywords or when prompt augmentation\n * produced a compliance-flavoured case.\n */\n regulatoryRelevance?: RegulatoryRelevance;\n}\n\n/** Wrapper produced by the generator for a single job. */\nexport interface GeneratedTestCaseList {\n schemaVersion: typeof GENERATED_TEST_CASE_SCHEMA_VERSION;\n jobId: string;\n testCases: GeneratedTestCase[];\n}\n\n/** Reason a fallback visual sidecar deployment was selected, if any. */\nexport type VisualSidecarFallbackReason =\n | \"primary_unavailable\"\n | \"primary_quota_exceeded\"\n | \"primary_disabled\"\n | \"policy_downgrade\"\n | \"none\";\n\n/**\n * Schema version for the persisted multimodal visual sidecar result\n * artifact emitted by the visual sidecar client (Issue #1386). Bumped\n * independently from `VISUAL_SIDECAR_SCHEMA_VERSION` (which describes the\n * sidecar's per-screen output) because this version covers the wrapping\n * envelope plus capture identities, attempts, and failure classes.\n */\nexport const VISUAL_SIDECAR_RESULT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted visual sidecar result artifact. */\nexport const VISUAL_SIDECAR_RESULT_ARTIFACT_FILENAME =\n \"visual-sidecar-result.json\" as const;\n\n/**\n * Canonical directory for per-attempt visual sidecar diagnostic artifacts\n * (Issue #2017). Each failed attempt writes a single JSON file under this\n * directory with the raw normalized response shape, redacted gateway\n * message, and structured parser error so a reviewer can debug the model\n * response after the fact without re-running the live LLM call.\n */\nexport const VISUAL_SIDECAR_DIAGNOSTICS_ARTIFACT_DIRECTORY =\n \"visual-sidecar-diagnostics\" as const;\n\n/** Stable schema version for the persisted visual sidecar diagnostic artifact. */\nexport const VISUAL_SIDECAR_DIAGNOSTIC_ARTIFACT_SCHEMA_VERSION =\n \"1.0.0\" as const;\n\n/**\n * Persisted form of a single visual sidecar attempt diagnostic. Carries\n * sanitized, bounded fragments of the gateway response so the failure\n * can be debugged from the artifact alone.\n */\nexport interface VisualSidecarDiagnosticArtifact {\n schemaVersion: typeof VISUAL_SIDECAR_DIAGNOSTIC_ARTIFACT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n visualSidecarSchemaVersion: typeof VISUAL_SIDECAR_SCHEMA_VERSION;\n jobId: string;\n generatedAt: string;\n /** 1-based attempt index across primary + fallback. Matches `VisualSidecarAttempt.attempt`. */\n attempt: number;\n /** Deployment that produced the response. */\n deployment: SidecarDeployment;\n /** Wall-clock duration of the attempt in milliseconds. */\n durationMs: number;\n /** Error class for the attempt. */\n errorClass: LlmGatewayErrorClass | \"schema_invalid_response\";\n /** Bounded, sanitized parser-error description (matches `VisualSidecarAttempt.normalizedParserError`). */\n normalizedParserError?: string;\n /** Bounded, sanitized gateway error message captured from the failed `LlmGenerationFailure`. */\n gatewayMessage?: string;\n /**\n * Coarse shape classification of the response payload:\n * - `\"string\"` — gateway returned a string (e.g. raw JSON text).\n * - `\"object\"` — gateway returned a JSON object payload.\n * - `\"array\"` — gateway returned a JSON array payload.\n * - `\"null\"` — gateway returned an explicit JSON `null` payload.\n * - `\"missing\"` — the attempt failed before any content was produced\n * (transport, timeout, canceled, refusal, etc.).\n */\n responseShape: \"string\" | \"object\" | \"array\" | \"null\" | \"missing\";\n /**\n * Bounded, redacted slice of the gateway response. Capped at\n * `VISUAL_SIDECAR_DIAGNOSTIC_RAW_TEXT_BYTE_LIMIT` bytes (UTF-8). String\n * payloads are persisted verbatim; object/array payloads are\n * canonical-JSON-stringified first. Absent when no content is\n * available (transport / canceled / refusal failures, or when\n * serialization fails).\n */\n rawTextContent?: string;\n /** Hard invariant — image bytes are never embedded in this artifact. */\n rawScreenshotsIncluded: false;\n}\n\n/**\n * UTF-8 byte cap on `rawTextContent` of a diagnostic artifact. Five\n * kilobytes is enough to fit a typical malformed sidecar envelope plus\n * the surrounding chatter, while small enough to keep the artifact\n * well below filesystem and review-tool friction thresholds.\n */\nexport const VISUAL_SIDECAR_DIAGNOSTIC_RAW_TEXT_BYTE_LIMIT = 5_120 as const;\n\n/**\n * Allowed failure classes for the visual sidecar client. The classes are\n * disjoint and policy-readable: a downstream policy gate can refuse a job\n * by inspecting the failure class without reading sanitized free-form\n * messages.\n */\nexport const ALLOWED_VISUAL_SIDECAR_FAILURE_CLASSES = [\n \"primary_unavailable\",\n \"primary_quota_exceeded\",\n \"both_sidecars_failed\",\n \"schema_invalid_response\",\n \"image_payload_too_large\",\n \"image_mime_unsupported\",\n \"duplicate_screen_id\",\n \"empty_screen_capture_set\",\n] as const;\n\n/** Discriminant of a `VisualSidecarFailure`. */\nexport type VisualSidecarFailureClass =\n (typeof ALLOWED_VISUAL_SIDECAR_FAILURE_CLASSES)[number];\n\n/**\n * Allowed input MIME types for visual sidecar captures. SVG is intentionally\n * NOT in the allowlist because SVG is XML and exposes a parser/injection\n * surface that the multimodal sidecar should never have to evaluate.\n */\nexport const ALLOWED_VISUAL_SIDECAR_INPUT_MIME_TYPES = [\n \"image/png\",\n \"image/jpeg\",\n \"image/webp\",\n \"image/gif\",\n] as const;\n\n/** Discriminant of an allowed visual sidecar input MIME type. */\nexport type VisualSidecarInputMimeType =\n (typeof ALLOWED_VISUAL_SIDECAR_INPUT_MIME_TYPES)[number];\n\n/**\n * Maximum decoded byte size of a single visual sidecar capture. The bound\n * is enforced AFTER base64 decoding (i.e. on the actual image bytes the\n * gateway would forward). Five MiB matches the conservative ceiling Azure\n * OpenAI imposes on multimodal payloads.\n */\nexport const MAX_VISUAL_SIDECAR_INPUT_BYTES: number = 5 * 1024 * 1024;\n\n/**\n * In-memory capture handed to the visual sidecar client. The bytes never\n * touch disk: only the SHA-256 hash is persisted into the result artifact.\n */\nexport interface VisualSidecarCaptureInput {\n /** Stable identifier matching a `BusinessTestIntentScreen.screenId`. */\n screenId: string;\n /** MIME type of the encoded bytes. Must be in the allowlist. */\n mimeType: VisualSidecarInputMimeType;\n /** Base64-encoded image bytes. Decoded length must be <= the byte bound. */\n base64Data: string;\n /** Optional human-readable label. */\n screenName?: string;\n /** Optional ISO-8601 capture timestamp (sourced from a screenshot pipeline). */\n capturedAt?: string;\n /**\n * Optional decoded pixel dimensions. When present they are forwarded into\n * {@link LlmImageInput.widthPx}/{@link LlmImageInput.heightPx} so the\n * gateway's input-budget guard can apply tile-based token estimation\n * (Issue #1930) instead of charging the raw base64 byte length.\n */\n widthPx?: number;\n heightPx?: number;\n}\n\n/**\n * Identity record for a single capture, persisted alongside the sidecar\n * result. Carries no image bytes — only a SHA-256 of the decoded bytes\n * plus the byte length. Re-validating a result against the original\n * captures requires re-hashing, never re-loading raw screenshot bytes.\n */\nexport interface VisualSidecarCaptureIdentity {\n screenId: string;\n mimeType: VisualSidecarInputMimeType;\n byteLength: number;\n /** SHA-256 hex of the decoded image bytes (NOT of the base64 string). */\n sha256: string;\n}\n\n/**\n * Single attempt against a sidecar deployment. Composes with the gateway\n * surface so the policy gate can correlate attempts with the gateway's\n * own circuit-breaker telemetry without a translation layer.\n */\nexport interface VisualSidecarAttempt {\n /** Sidecar deployment that was attempted. */\n deployment: SidecarDeployment;\n /** Sequence index, 1-based across both primary and fallback attempts. */\n attempt: number;\n /** Wall-clock duration of the attempt in milliseconds. */\n durationMs: number;\n /**\n * Circuit-breaker state observed immediately before this attempt was\n * dispatched. Absent when the caller did not wire a caller-side breaker.\n */\n circuitBreakerState?: \"closed\" | \"open\" | \"half_open\";\n /** Error class when the attempt failed. Absent on a success. */\n errorClass?: LlmGatewayErrorClass | \"schema_invalid_response\";\n /**\n * Issue #2017: bounded, sanitized parser-error description for failed\n * attempts. Populated whenever the sidecar response could be obtained\n * but failed structural normalization (e.g. missing `screens`, wrong\n * length, schema-invalid record). Always passes through\n * `redactHighRiskSecrets` and is capped to a small byte budget so the\n * field is safe to surface in artifacts and dashboards.\n */\n normalizedParserError?: string;\n /**\n * Issue #2017: relative path (within the run directory) of the\n * persisted raw-response diagnostic artifact for a failed attempt.\n * Always points to a JSON file under\n * `visual-sidecar-diagnostics/`. Absent on success and on pre-flight\n * failures where no gateway round-trip occurred.\n */\n rawResponseArtifactPath?: string;\n}\n\n/**\n * Successful sidecar outcome — primary or fallback. The downstream\n * `VisualScreenDescription[]` is structurally validated by the existing\n * `validateVisualSidecar` gate; this type carries the validation report\n * verbatim so the caller can persist or refuse on it.\n */\nexport interface VisualSidecarSuccess {\n outcome: \"success\";\n /** Deployment that produced the descriptions. */\n selectedDeployment: SidecarDeployment;\n fallbackReason: VisualSidecarFallbackReason;\n visual: VisualScreenDescription[];\n captureIdentities: VisualSidecarCaptureIdentity[];\n attempts: VisualSidecarAttempt[];\n /** Aggregated confidence summary across every screen description. */\n confidenceSummary: { min: number; max: number; mean: number };\n /**\n * Verbatim validation report produced by `validateVisualSidecar`. The\n * client does NOT silently strip findings — when the report says\n * `blocked: true`, the success surfaces the report so the caller can\n * persist it for the policy gate to inspect.\n */\n validationReport: VisualSidecarValidationReport;\n}\n\n/**\n * Failure outcome — both primary and fallback exhausted, or pre-flight\n * rejected the captures. The `failureClass` is policy-readable so\n * upstream gates can decide between \"retry later\" and \"refuse the job\".\n */\nexport interface VisualSidecarFailure {\n outcome: \"failure\";\n failureClass: VisualSidecarFailureClass;\n /** Sanitized human-readable message — never carries tokens or PII. */\n failureMessage: string;\n attempts: VisualSidecarAttempt[];\n captureIdentities: VisualSidecarCaptureIdentity[];\n}\n\n/** Discriminated union returned by `describeVisualScreens`. */\nexport type VisualSidecarResult = VisualSidecarSuccess | VisualSidecarFailure;\n\n/**\n * Persisted form of the visual sidecar result. Carries schema/contract\n * stamps and the hard `rawScreenshotsIncluded: false` literal so that any\n * downstream consumer can verify the artifact never re-introduced raw\n * screenshot bytes.\n */\nexport interface VisualSidecarResultArtifact {\n schemaVersion: typeof VISUAL_SIDECAR_RESULT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n visualSidecarSchemaVersion: typeof VISUAL_SIDECAR_SCHEMA_VERSION;\n jobId: string;\n generatedAt: string;\n result: VisualSidecarResult;\n /**\n * Deterministic derivative evidence refs extracted from the validation\n * report. Each ref hashes the canonical tuple\n * `(screenId|deployment|sortedOutcomes|roundedConfidence)` so downstream\n * audit consumers can correlate prompt-time visual evidence without relying\n * on raw screenshot-byte hashes.\n */\n visualEvidenceRefs?: {\n screenId: string;\n modelDeployment: string;\n evidenceHash: string;\n }[];\n /** Hard invariant — image bytes are never embedded in this artifact. */\n rawScreenshotsIncluded: false;\n}\n\n/**\n * Identity of the visual sidecar that produced a `VisualScreenDescription`\n * batch. The compiler hashes this object into the replay-cache key so that\n * a fallback model swap forces a cache miss.\n */\nexport interface CompiledPromptVisualBinding {\n schemaVersion: typeof VISUAL_SIDECAR_SCHEMA_VERSION;\n selectedDeployment: SidecarDeployment;\n fallbackReason: VisualSidecarFallbackReason;\n /** Hex digest of the screenshot/fixture used for visual analysis, if any. */\n fixtureImageHash?: string;\n /** Number of screens covered by the visual binding. */\n screenCount: number;\n}\n\n/** Identity of the structured-test-case generator gateway/model pair. */\nexport interface CompiledPromptModelBinding {\n modelRevision: string;\n gatewayRelease: string;\n /** Optional deterministic seed the model accepts (provider-dependent). */\n seed?: number;\n}\n\n/** Hash bundle attached to a compiled prompt. */\nexport interface CompiledPromptHashes {\n inputHash: string;\n promptHash: string;\n schemaHash: string;\n cacheKey: string;\n cacheablePrefixHash: string;\n contextBudgetHash?: string;\n}\n\n/** Sanitized custom supporting context visible to prompt compilation. */\nexport interface CompiledPromptCustomContext {\n markdownSections: Array<{\n sourceId: string;\n entryId: string;\n bodyMarkdown: string;\n bodyPlain: string;\n markdownContentHash: string;\n plainContentHash: string;\n }>;\n structuredAttributes: Array<{\n sourceId: string;\n entryId: string;\n key: string;\n value: string;\n contentHash: string;\n }>;\n}\n\n/** Persisted, fully-redacted artifact form of a compiled prompt. */\nexport interface CompiledPromptArtifacts {\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n promptTemplateVersion: typeof TEST_INTELLIGENCE_PROMPT_TEMPLATE_VERSION;\n schemaVersion: typeof GENERATED_TEST_CASE_SCHEMA_VERSION;\n redactionPolicyVersion: typeof REDACTION_POLICY_VERSION;\n jobId: string;\n systemPrompt: string;\n userPrompt: string;\n /** Redacted JSON payload that the model will reason over. */\n payload: {\n intent: BusinessTestIntentIr;\n visual: VisualScreenDescription[];\n testDesignModel?: TestDesignModel;\n coveragePlan?: CoveragePlan;\n workflowTopology?: WorkflowTopology;\n riskRanking?: RiskRanking;\n customerRubric?: Record<string, unknown>;\n customContext?: CompiledPromptCustomContext;\n sourceMixPlan?: SourceMixPlan;\n };\n hashes: CompiledPromptHashes;\n promptLayout: {\n prefix: string;\n suffix: string;\n prefixEndMarker: \"--- prefix end ---\";\n };\n visualBinding: CompiledPromptVisualBinding;\n modelBinding: CompiledPromptModelBinding;\n policyBundleVersion: string;\n}\n\n/** Wire-shaped request handed to the LLM gateway client. */\nexport interface CompiledPromptRequest {\n jobId: string;\n modelBinding: CompiledPromptModelBinding;\n systemPrompt: string;\n userPrompt: string;\n /** JSON schema the gateway must enforce on the response (structured output). */\n responseSchema: Record<string, unknown>;\n /** Stable schema name used by some gateways. */\n responseSchemaName: string;\n hashes: CompiledPromptHashes;\n}\n\n/**\n * Multi-tenant scope (Issue #1944).\n *\n * Identifies the tenant + environment + (optional) project that owns a cache\n * partition. Filesystem caches under\n * `<root>/replay-cache/<tenantId>/<environmentId>/<projectId>/…` are bound to\n * exactly one `TenantScope` at construction time; cross-tenant reads are\n * impossible because the loader has no API to address paths outside its\n * scope. Existing single-tenant deployments map to {@link DEFAULT_TENANT_SCOPE}.\n *\n * Each segment is treated as a single path component. The runtime rejects\n * empty values and any segment containing a path separator (`/`, `\\`) or the\n * `..` traversal token — see `resolveTenantScopeSegments` in\n * `src/test-intelligence/replay-cache.ts`.\n */\nexport interface TenantScope {\n /** Stable, non-PII tenant identifier (e.g. customer org id). */\n readonly tenantId: string;\n /** Environment identifier (e.g. `prod`, `staging`, `dev`). */\n readonly environmentId: string;\n /**\n * Optional project identifier within the tenant. When omitted the loader\n * substitutes `\"default\"` so the on-disk path is always three segments\n * deep — preventing accidental collision when a project id is later added.\n */\n readonly projectId?: string;\n}\n\n/**\n * Sentinel `TenantScope` used by single-tenant callers that have not yet\n * adopted the structured scope (Issue #1944). Keeps the on-disk layout\n * `<root>/default/default/default/…` so an unscoped caller's cache is\n * isolated from any scoped caller's cache by directory boundary.\n */\nexport const DEFAULT_TENANT_SCOPE: TenantScope = {\n tenantId: \"default\",\n environmentId: \"default\",\n projectId: \"default\",\n};\n\n/** Replay-cache key — the only deterministic-bit-identical replay anchor. */\nexport interface ReplayCacheKey {\n inputHash: string;\n promptHash: string;\n schemaHash: string;\n /** sha256 hex digest of the canonical resolved model-routing policy. */\n routingPolicyDigest: string;\n modelRevision: string;\n gatewayRelease: string;\n policyBundleVersion: string;\n redactionPolicyVersion: typeof REDACTION_POLICY_VERSION;\n visualSidecarSchemaVersion: typeof VISUAL_SIDECAR_SCHEMA_VERSION;\n visualSelectedDeployment: CompiledPromptVisualBinding[\"selectedDeployment\"];\n visualFallbackReason: VisualSidecarFallbackReason;\n fixtureImageHash?: string;\n promptTemplateVersion: typeof TEST_INTELLIGENCE_PROMPT_TEMPLATE_VERSION;\n cacheablePrefixHash: string;\n seed?: number;\n sourceMixPlanHash?: string;\n contextBudgetHash?: string;\n deterministicPostProcessingVersion?: string;\n constrainedDecodingAdapterId?: LlmConstrainedDecodingAdapterId;\n constrainedDecodingAdapterVersion?: string;\n constrainedDecodingFallbackReason?: string;\n}\n\n/** Minimal persisted metadata for one compiled role-step prompt run. */\nexport interface AgentRoleRunArtifact {\n schemaVersion: typeof AGENT_ROLE_RUN_SCHEMA_VERSION;\n jobId: string;\n roleRunId: string;\n roleStepId: string;\n parentJobId?: string;\n roleLineageDepth?: number;\n promptTemplateVersion: typeof TEST_INTELLIGENCE_PROMPT_TEMPLATE_VERSION;\n cacheablePrefixHash: string;\n promptHash: string;\n schemaHash: string;\n inputHash: string;\n cacheKeyDigest: string;\n rawPromptsIncluded: false;\n}\n\nexport interface GenealogyArtifactNode {\n readonly jobId: string;\n readonly roleStepId: string;\n readonly artifactFilename: string;\n readonly parentJobId?: string;\n readonly roleLineageDepth?: number;\n}\n\nexport interface GenealogyArtifact {\n readonly schemaVersion: typeof GENEALOGY_SCHEMA_VERSION;\n readonly generatedAt: string;\n readonly nodes: readonly GenealogyArtifactNode[];\n}\n\n/** Stored cache entry. */\nexport interface ReplayCacheEntry {\n key: string;\n storedAt: string;\n testCases: GeneratedTestCaseList;\n}\n\n/** Cache lookup outcome consumed by the orchestration layer. */\nexport type ReplayCacheLookupResult =\n | { hit: true; entry: ReplayCacheEntry }\n | { hit: false; key: string };\n\n/**\n * Review gate + export-only QC artifact surface (Issue #1365 / #1376).\n *\n * The review gate persists per-test-case lifecycle decisions made by a\n * reviewer (or by the policy gate, when policy auto-approves) so that\n * downstream export operations can refuse to produce QC/ALM artifacts\n * for cases that have not been approved. Wave 1 (#1365) ships:\n *\n * - in-memory state machine (`generated → needs_review → approved |\n * rejected | edited → exported → transferred`),\n * - file-system review store (event log + snapshot),\n * - bearer-protected handler that mirrors the import-session\n * governance pattern (`validateImportSessionEventWriteAuth`),\n * - deterministic export pipeline emitting `testcases.json`,\n * `testcases.csv`, `testcases.alm.xml`, `qc-mapping-preview.json`,\n * `export-report.json`, plus optional `testcases.xlsx`.\n *\n * Wave 2 (#1376) adds server-side four-eyes enforcement for cases whose\n * risk category is configured as high-risk OR whose multimodal visual\n * sidecar workflow surfaced low-confidence / fallback / PII /\n * prompt-injection / Figma-conflict signals. When four-eyes is\n * enforced, two distinct authenticated principals must approve before\n * the case may transition to `approved`. The intermediate state\n * `pending_secondary_approval` records the first approval; export and\n * ALM transfer paths refuse cases that did not reach `approved`.\n *\n * No production QC/ALM API write is performed; the surface only\n * persists artifacts to disk for downstream operators to upload.\n */\n\n/**\n * Allowed lifecycle states for a generated test case under review.\n *\n * `pending_secondary_approval` (added in #1376) is the intermediate\n * state a four-eyes-enforced case occupies after the first approval and\n * before the second distinct approval. Cases not subject to four-eyes\n * skip this state entirely.\n */\nexport const ALLOWED_REVIEW_STATES = [\n \"generated\",\n \"needs_review\",\n \"pending_secondary_approval\",\n \"approved\",\n \"rejected\",\n \"edited\",\n \"exported\",\n \"transferred\",\n] as const;\nexport type ReviewState = (typeof ALLOWED_REVIEW_STATES)[number];\n\n/**\n * Allowed event kinds appended to the review-gate event log.\n *\n * `primary_approved` and `secondary_approved` (added in #1376) are\n * emitted in lockstep with four-eyes enforcement: the first distinct\n * approver records `primary_approved`; the second distinct approver\n * records `secondary_approved`. Clients may also continue to send the\n * generic `approved` kind — when the snapshot indicates four-eyes is\n * enforced, the store routes the request to the correct primary or\n * secondary event kind based on current state, which keeps wire-level\n * audit clarity without forcing UI rewrites.\n */\nexport const ALLOWED_REVIEW_EVENT_KINDS = [\n \"generated\",\n \"review_started\",\n \"approved\",\n \"primary_approved\",\n \"secondary_approved\",\n \"rejected\",\n \"edited\",\n \"exported\",\n \"transferred\",\n \"note\",\n] as const;\nexport type ReviewEventKind = (typeof ALLOWED_REVIEW_EVENT_KINDS)[number];\n\n/**\n * Reasons four-eyes review is enforced for a single test case (#1376).\n *\n * Multiple reasons may apply (e.g. a `regulated_data` case whose visual\n * sidecar reported low confidence). Reasons are reported deterministic-\n * sorted on the `ReviewSnapshot.fourEyesReasons` field.\n */\nexport const ALLOWED_FOUR_EYES_ENFORCEMENT_REASONS = [\n \"risk_category\",\n \"visual_low_confidence\",\n \"visual_fallback_used\",\n \"visual_possible_pii\",\n \"visual_prompt_injection\",\n \"visual_metadata_conflict\",\n \"multi_source_conflict_present\",\n] as const;\nexport type FourEyesEnforcementReason =\n (typeof ALLOWED_FOUR_EYES_ENFORCEMENT_REASONS)[number];\n\n/**\n * Default risk categories that require four-eyes review (#1376).\n *\n * The list spans the existing `TestCaseRiskCategory` taxonomy. Issue\n * #1376 names the operator-facing risk classes as\n * `payment / authorization / identity / regulatory`; those map onto the\n * existing taxonomy as `financial_transaction` (payment) +\n * `regulated_data` (identity, regulatory) + `high` (authorization /\n * elevated-impact). Operators may override with\n * `WorkspaceStartOptions.testIntelligence.fourEyesRequiredRiskCategories`.\n */\nexport const DEFAULT_FOUR_EYES_REQUIRED_RISK_CATEGORIES: readonly TestCaseRiskCategory[] =\n [\"financial_transaction\", \"regulated_data\", \"high\"];\n\n/**\n * Default visual-sidecar validation outcomes that trigger four-eyes\n * enforcement (#1376, 2026-04-24 multimodal addendum).\n *\n * When ANY screen referenced by a test case carries one of these\n * outcomes in `VisualSidecarValidationReport`, the case is enforced as\n * four-eyes regardless of risk category.\n */\nexport const DEFAULT_FOUR_EYES_VISUAL_SIDECAR_TRIGGERS: readonly VisualSidecarValidationOutcome[] =\n [\n \"low_confidence\",\n \"fallback_used\",\n \"possible_pii\",\n \"prompt_injection_like_text\",\n \"conflicts_with_figma_metadata\",\n ];\n\n/**\n * Operator-tunable four-eyes policy (#1376).\n *\n * Resolved at startup from `WorkspaceStartOptions.testIntelligence`\n * fields; the resolved policy is consulted at review-snapshot seed time\n * to stamp `fourEyesEnforced` per test case.\n */\nexport interface FourEyesPolicy {\n /** Risk categories that always require four-eyes. Sorted, deduplicated. */\n requiredRiskCategories: readonly TestCaseRiskCategory[];\n /**\n * Visual-sidecar validation outcomes that trigger four-eyes regardless\n * of risk category. Sorted, deduplicated.\n */\n visualSidecarTriggerOutcomes: readonly VisualSidecarValidationOutcome[];\n}\n\n/** Allowed reasons the export pipeline may refuse to emit QC artifacts. */\nexport const ALLOWED_EXPORT_REFUSAL_CODES = [\n \"no_approved_test_cases\",\n \"unapproved_test_cases_present\",\n \"policy_blocked_cases_present\",\n \"schema_invalid_cases_present\",\n \"visual_sidecar_blocked\",\n \"review_state_inconsistent\",\n] as const;\nexport type ExportRefusalCode = (typeof ALLOWED_EXPORT_REFUSAL_CODES)[number];\n\n/** Single immutable event appended to the review-gate event log. */\nexport interface ReviewEvent {\n schemaVersion: typeof REVIEW_GATE_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n /** Globally unique opaque identifier; generated server-side. */\n id: string;\n jobId: string;\n /** Unset when the event is job-level (e.g. seed). */\n testCaseId?: string;\n kind: ReviewEventKind;\n /** ISO-8601 UTC timestamp at the moment of persistence. */\n at: string;\n /** Optional opaque actor handle; never an email or token. */\n actor?: string;\n /** Optional human-readable note (length-bounded by the store). */\n note?: string;\n fromState?: ReviewState;\n toState?: ReviewState;\n /** Monotonic 1-based per-job sequence; gap-free. */\n sequence: number;\n /** Flat metadata (no nested objects). */\n metadata?: Record<string, string | number | boolean | null>;\n}\n\n/** Per-test-case review-state snapshot. */\nexport interface ReviewSnapshot {\n testCaseId: string;\n state: ReviewState;\n policyDecision: TestCasePolicyDecision;\n /** Identifier of the most recent event affecting this case. */\n lastEventId: string;\n lastEventAt: string;\n /**\n * Whether the resolved four-eyes policy requires two distinct\n * authenticated principals before this case may reach `approved`.\n * When `true`, the export pipeline refuses cases not in `approved`,\n * `exported`, or `transferred` state (#1376).\n */\n fourEyesEnforced: boolean;\n /**\n * Set of distinct reviewer actors that have approved this case in\n * sequence. Sorted, deduplicated. For four-eyes-enforced cases the\n * first entry is the primary approver, the second the secondary.\n */\n approvers: string[];\n /**\n * Reasons four-eyes is enforced (#1376). Empty when\n * `fourEyesEnforced=false`. Sorted deterministic. Optional for\n * backward compatibility; consumers should treat absence as\n * \"no recorded reasons\" (i.e. older snapshots before #1376 shipped).\n */\n fourEyesReasons?: FourEyesEnforcementReason[];\n /**\n * Identity of the first distinct approver, recorded when a four-eyes\n * case transitions out of `needs_review`/`edited`. Optional for\n * non-enforced cases and for snapshots written before any approval.\n */\n primaryReviewer?: string;\n /** ISO-8601 UTC timestamp at which the primary approval was recorded. */\n primaryApprovalAt?: string;\n /**\n * Identity of the second distinct approver, recorded when a four-eyes\n * case transitions from `pending_secondary_approval` to `approved`.\n */\n secondaryReviewer?: string;\n /** ISO-8601 UTC timestamp at which the secondary approval was recorded. */\n secondaryApprovalAt?: string;\n /**\n * Identity of the actor who recorded the most recent `edited` event\n * for this case, if any. Used by the four-eyes gate to refuse\n * approvals submitted by the same principal that authored the edit\n * (self-approval refusal).\n */\n lastEditor?: string;\n}\n\n/** Aggregate per-job review-gate snapshot. */\nexport interface ReviewGateSnapshot {\n schemaVersion: typeof REVIEW_GATE_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n jobId: string;\n generatedAt: string;\n /** Sorted by `testCaseId` for deterministic emission. */\n perTestCase: ReviewSnapshot[];\n /** Number of cases currently in `approved` (or `exported`/`transferred`) state. */\n approvedCount: number;\n /** Number of cases currently in `needs_review` state. */\n needsReviewCount: number;\n /** Number of cases currently in `rejected` state. */\n rejectedCount: number;\n /**\n * Number of cases currently awaiting a second distinct approver\n * (state = `pending_secondary_approval`). Optional for backward\n * compatibility; consumers must treat absence as `0` (#1376).\n */\n pendingSecondaryApprovalCount?: number;\n /**\n * Resolved four-eyes policy that produced this snapshot. Optional for\n * backward compatibility. When present, both arrays are sorted /\n * deduplicated (#1376).\n */\n fourEyesPolicy?: FourEyesPolicy;\n}\n\n/** Visual provenance attached to a QC mapping preview entry (Issue #1386). */\nexport interface QcMappingVisualProvenance {\n deployment: SidecarDeployment | \"none\";\n fallbackReason: VisualSidecarFallbackReason;\n confidenceMean: number;\n ambiguityCount: number;\n /**\n * SHA-256 hex of the derived validation-record identity tuple\n * `(screenId|deployment|sortedOutcomes|roundedConfidence)`. This is not a\n * raw screenshot hash and does not include request headers or secrets.\n */\n evidenceHash: string;\n}\n\n/** Single per-test-case mapping preview row consumed by QC/ALM operators. */\nexport interface QcMappingPreviewEntry {\n testCaseId: string;\n /** Deterministic candidate external id used for idempotent later transfer. */\n externalIdCandidate: string;\n testName: string;\n objective: string;\n priority: TestCasePriority;\n riskCategory: TestCaseRiskCategory;\n /** Forward-slash-separated folder path under the profile root. */\n targetFolderPath: string;\n preconditions: string[];\n testData: string[];\n designSteps: GeneratedTestCaseStep[];\n expectedResults: string[];\n /** Subset of figmaTraceRefs sufficient for round-trip provenance. */\n sourceTraceRefs: GeneratedTestCaseFigmaTrace[];\n /** Clarifies that `exportable` is mapping-preview-only, not final export approval. */\n decisionBasis?: \"mapping_preview_only\";\n exportable: boolean;\n blockingReasons: string[];\n visualProvenance?: QcMappingVisualProvenance;\n}\n\n/** Aggregate QC mapping preview artifact. */\nexport interface QcMappingPreviewArtifact {\n schemaVersion: typeof QC_MAPPING_PREVIEW_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n jobId: string;\n generatedAt: string;\n profileId: string;\n profileVersion: string;\n /** Sorted by `testCaseId` for deterministic emission. */\n entries: QcMappingPreviewEntry[];\n}\n\n/** Operator-tunable knobs of an OpenText ALM reference export profile. */\nexport interface OpenTextAlmExportProfile {\n id: string;\n version: string;\n description: string;\n /** Folder path prepended to every per-case `targetFolderPath`. */\n rootFolderPath: string;\n /**\n * Whether to wrap the test-case description in a CDATA block so that\n * embedded markup survives ALM round-trips.\n */\n cdataDescription: boolean;\n}\n\n/** Allowed content types declared on an exported artifact record. */\nexport const ALLOWED_EXPORT_ARTIFACT_CONTENT_TYPES = [\n \"application/json\",\n \"text/csv\",\n \"application/xml\",\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n] as const;\nexport type ExportArtifactContentType =\n (typeof ALLOWED_EXPORT_ARTIFACT_CONTENT_TYPES)[number];\n\n/** Single artifact bookkeeping row inside `export-report.json`. */\nexport interface ExportArtifactRecord {\n filename: string;\n /** SHA-256 hex of the on-disk byte stream. */\n sha256: string;\n bytes: number;\n contentType: ExportArtifactContentType;\n}\n\n/** Aggregate export-report artifact. */\nexport interface ExportReportArtifact {\n schemaVersion: typeof EXPORT_REPORT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n jobId: string;\n generatedAt: string;\n profileId: string;\n profileVersion: string;\n /** Identity of the deployments behind the run. */\n modelDeployments: {\n testGeneration: string;\n visualPrimary?: SidecarDeployment | \"none\";\n visualFallback?: SidecarDeployment | \"none\";\n };\n exportedTestCaseCount: number;\n /**\n * Explicit final export decision derived from validation, policy, visual,\n * and review-gate outcomes rather than from per-case mapping preview flags.\n */\n finalExportDecision: \"approved_for_export\" | \"refused\";\n /** True when the pipeline refused to emit any non-report artifact. */\n refused: boolean;\n refusalCodes: ExportRefusalCode[];\n /** Sorted by filename for deterministic emission. */\n artifacts: ExportArtifactRecord[];\n /** Sorted, de-duplicated. */\n visualEvidenceHashes: string[];\n /** Hard invariant: raw screenshots are never embedded into export artifacts. */\n rawScreenshotsIncluded: false;\n}\n\n/* ------------------------------------------------------------------ */\n/* Wave 1 Validation evidence manifest + evaluation report (Issue #1366) */\n/* ------------------------------------------------------------------ */\n\n/** Schema version for the Wave 1 Validation evidence manifest envelope. */\nexport const WAVE1_VALIDATION_EVIDENCE_MANIFEST_SCHEMA_VERSION =\n \"1.0.0\" as const;\n\n/** Filename used for the Wave 1 Validation evidence manifest artifact. */\nexport const WAVE1_VALIDATION_EVIDENCE_MANIFEST_ARTIFACT_FILENAME =\n \"wave1-validation-evidence-manifest.json\";\n\n/** Filename used for the persisted per-run W3C PROV JSON-LD graph. */\nexport const PROVENANCE_ARTIFACT_FILENAME = \"provenance.jsonld\" as const;\n\n/** Schema version for the persisted test-intelligence provenance artifact. */\nexport const TEST_INTELLIGENCE_PROVENANCE_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Filename used for the Wave 1 Validation evidence manifest digest witness. */\nexport const WAVE1_VALIDATION_EVIDENCE_MANIFEST_DIGEST_FILENAME =\n \"wave1-validation-evidence-manifest.sha256\";\n\n/** Schema version for the Wave 1 Validation evaluation report envelope. */\nexport const WAVE1_VALIDATION_EVAL_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Filename used for the Wave 1 Validation evaluation report artifact. */\nexport const WAVE1_VALIDATION_EVAL_REPORT_ARTIFACT_FILENAME =\n \"wave1-validation-eval-report.json\";\n\n/** Schema version for the AgentLessons eval report envelope. */\nexport const AGENT_LESSONS_EVAL_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Filename used for the AgentLessons eval report artifact. */\nexport const AGENT_LESSONS_EVAL_REPORT_ARTIFACT_FILENAME =\n \"agent-lessons-eval-report.json\" as const;\n\n/**\n * Allowed Wave 1 Validation fixture identifiers.\n *\n * `validation-onboarding` — synthetic onboarding-style sign-up flow.\n * `validation-payment-auth` — synthetic payment + 3-D Secure authorisation flow.\n *\n * Both fixtures are public, contain only synthetic data, and ship with a\n * companion visual sidecar fixture so the Figma → Visual Sidecar →\n * Business Test Intent IR → structured generation chain is exercised\n * end-to-end against an air-gapped mock LLM.\n */\nexport const WAVE1_VALIDATION_FIXTURE_IDS = [\n \"validation-onboarding\",\n \"validation-payment-auth\",\n] as const;\n\n/** Identifier of a Wave 1 Validation fixture. */\nexport type Wave1ValidationFixtureId =\n (typeof WAVE1_VALIDATION_FIXTURE_IDS)[number];\n\n/** Categorisation of an artifact attested by the evidence manifest. */\nexport type Wave1ValidationEvidenceArtifactCategory =\n | \"intent\"\n | \"validation\"\n | \"review\"\n | \"export\"\n | \"manifest\"\n | \"genealogy\"\n | \"visual_sidecar\"\n | \"finops\"\n | \"attestation\"\n | \"signature\"\n | \"lbom\"\n | \"ml_bom\"\n | \"self_verify_rubric\"\n | \"intent_delta\"\n | \"dedupe_report\"\n | \"traceability_matrix\"\n | \"multi_source_reconciliation\"\n | \"source_ir\"\n | \"source_provenance\"\n | \"multi_source_conflicts\"\n | \"production_readiness_eval\";\n\n/** Single artifact attested by the Wave 1 Validation evidence manifest. */\nexport interface Wave1ValidationEvidenceArtifact {\n /** Relative filename inside the run directory. */\n filename: string;\n /** SHA-256 of the on-disk byte stream. */\n sha256: string;\n /** Byte length on disk at manifest creation time. */\n bytes: number;\n category: Wave1ValidationEvidenceArtifactCategory;\n /**\n * Issue #2177 — per-artifact EU data-residency attestations. One entry per\n * LLM call or cache hit that contributed to the artifact.\n */\n regionAttestations?: readonly RegionAttestation[];\n}\n\n/**\n * Result of `verifyWave1ValidationEvidenceManifest` against a directory of artifacts.\n * Determines whether ALL attested artifacts still hash to the values stored\n * in the manifest. Any mismatch fails the verification fail-closed.\n */\nexport interface Wave1ValidationEvidenceVerificationResult {\n ok: boolean;\n /** Filenames listed in the manifest that are missing on disk. */\n missing: string[];\n /** Filenames whose on-disk SHA-256 differs from the manifest. */\n mutated: string[];\n /** Filenames whose on-disk byte length differs from the manifest. */\n resized: string[];\n /** Filenames present on disk but not attested by the manifest. */\n unexpected: string[];\n /**\n * Manifest self-attestation result when the manifest carries a\n * `manifestIntegrity` block, or when a current-version manifest is missing\n * the block and therefore fails closed.\n */\n manifestIntegrity?: Wave1ValidationEvidenceManifestIntegrityVerification;\n}\n\n/** Self-attestation stamped into the Wave 1 evidence manifest. */\nexport interface Wave1ValidationEvidenceManifestIntegrity {\n algorithm: \"sha256\";\n /** SHA-256 of canonical manifest JSON with `manifestIntegrity` omitted. */\n hash: string;\n}\n\n/** Structured verification result for the manifest self-attestation. */\nexport interface Wave1ValidationEvidenceManifestIntegrityVerification {\n algorithm: \"sha256\";\n actualHash: string;\n expectedHash?: string;\n ok: boolean;\n}\n\n/** Visual-sidecar summary duplicated into the Wave 1 evidence manifest. */\nexport interface Wave1ValidationEvidenceVisualSidecarSummary {\n selectedDeployment: SidecarDeployment;\n fallbackReason: VisualSidecarFallbackReason;\n confidenceSummary: { min: number; max: number; mean: number };\n /** SHA-256 hex of the persisted `visual-sidecar-result.json` artifact. */\n resultArtifactSha256: string;\n}\n\n/**\n * Wave 1 Validation evidence manifest. Frozen, deterministic, byte-identical\n * across runs of the same fixture and mock output. Lists every artifact\n * the harness emits with its SHA-256 hash and byte length, plus the\n * contract / template / schema / policy / model identities used during\n * the run. The manifest itself is also written to disk; verifying its\n * integrity is performed against the stored copy plus the artifact bytes.\n *\n * Two negative invariants are stamped explicitly so they appear in the\n * evidence audit trail rather than being inferred from absence:\n *\n * - `rawScreenshotsIncluded: false` — no raw screenshot bytes are ever\n * embedded in any exported artifact.\n * - `imagePayloadSentToTestGeneration: false` — the structured-test-case\n * generator deployment (e.g. `gpt-oss-120b`) never received an image\n * payload during the run; image-bearing payloads only flow into the\n * visual sidecar role.\n */\nexport interface Wave1ValidationEvidenceManifest {\n schemaVersion: typeof WAVE1_VALIDATION_EVIDENCE_MANIFEST_SCHEMA_VERSION;\n /** Contract version that produced the artifacts. */\n contractVersion: string;\n /** Test-intelligence subsurface contract version. */\n testIntelligenceContractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n /** Identifier of the fixture or runner profile exercised. */\n fixtureId: string;\n jobId: string;\n generatedAt: string;\n /** Versions used to compile the prompt and validate the output. */\n promptTemplateVersion: typeof TEST_INTELLIGENCE_PROMPT_TEMPLATE_VERSION;\n generatedTestCaseSchemaVersion: typeof GENERATED_TEST_CASE_SCHEMA_VERSION;\n visualSidecarSchemaVersion: typeof VISUAL_SIDECAR_SCHEMA_VERSION;\n redactionPolicyVersion: typeof REDACTION_POLICY_VERSION;\n /**\n * Version of the subprocessor register + cross-border transfer control set\n * active for the run. Carries DORA Art. 28 / GDPR Ch. V evidence identity\n * into the replay artifact.\n */\n subprocessorRegisterVersion: typeof SUBPROCESSOR_REGISTER_VERSION;\n /** Policy profile identity used by the validation pipeline. */\n policyProfileId: string;\n policyProfileVersion: string;\n /** OpenText ALM (or override) export profile identity. */\n exportProfileId: string;\n exportProfileVersion: string;\n /** Identities of the deployments behind the run. */\n modelDeployments: {\n testGeneration: string;\n visualPrimary?: SidecarDeployment | \"none\";\n visualFallback?: SidecarDeployment | \"none\";\n };\n /**\n * Active model-binding summary attested for the run. Under banking\n * profiles every active binding must carry `ictRegisterRef`.\n */\n activeModelBindings?: readonly ActiveModelBinding[];\n /** Replay-cache identity hashes for the run (mirrors compiled prompt). */\n promptHash: string;\n schemaHash: string;\n inputHash: string;\n cacheKeyDigest: string;\n /** Direct visual-sidecar evidence summary when the opt-in sidecar path ran. */\n visualSidecar?: Wave1ValidationEvidenceVisualSidecarSummary;\n /**\n * Persisted screenshot capture identities when the visual sidecar ran.\n * Carries only SHA-256 identities plus MIME type and byte length; raw\n * screenshot bytes are never embedded in the manifest.\n */\n visualSidecarCaptureIdentities?: VisualSidecarCaptureIdentity[];\n /**\n * Self-attestation over the canonical manifest metadata and artifact list.\n * New manifests stamp this field; it remains optional so legacy manifests can\n * still be parsed and verified with their existing digest witness.\n */\n manifestIntegrity?: Wave1ValidationEvidenceManifestIntegrity;\n /** Sorted-by-filename, de-duplicated artifact list. */\n artifacts: Wave1ValidationEvidenceArtifact[];\n /**\n * Per-source provenance records added when the multi-source pipeline ran.\n * Present only when `multiSourceEnabled` is `true`. Each entry records the\n * SHA-256 + bytes of the per-source IR artifact under\n * `<runDir>/sources/<sourceId>/`. Never includes raw Jira API responses,\n * raw paste bytes, or PII.\n */\n sourceProvenanceRecords?: MultiSourceSourceProvenanceRecord[];\n /** `true` when the Wave 4 multi-source pipeline produced this manifest. */\n multiSourceEnabled?: boolean;\n /** Hard invariant: no raw screenshot bytes leak into export artifacts. */\n rawScreenshotsIncluded: false;\n /**\n * Hard invariant: the structured-test-case generator deployment never\n * received an image payload during the run.\n */\n imagePayloadSentToTestGeneration: false;\n /** Hard invariant on multi-source manifests: raw Jira responses not persisted. */\n rawJiraResponsePersisted?: false;\n /** Hard invariant on multi-source manifests: raw paste bytes not persisted. */\n rawPasteBytesPersisted?: false;\n}\n\n/**\n * Numeric thresholds applied by the Wave 1 Validation evaluation gate. Each\n * threshold is enforced on a per-fixture basis. Fractions are in `[0, 1]`.\n */\nexport interface Wave1ValidationEvalThresholds {\n /** Fraction of detected fields covered by at least one approved test case. */\n minTraceCoverageFields: number;\n /** Fraction of detected actions covered by at least one approved test case. */\n minTraceCoverageActions: number;\n /** Fraction of detected validations covered by at least one approved test case. */\n minTraceCoverageValidations: number;\n /** Fraction of approved cases whose `qcMappingPreview.exportable` is true. */\n minQcMappingExportableFraction: number;\n /**\n * Maximum allowed pairwise duplicate similarity across all generated cases.\n * Computed by `detectDuplicateTestCases` on case fingerprints.\n */\n maxDuplicateSimilarity: number;\n /** Minimum number of `expectedResults` entries required per approved case. */\n minExpectedResultsPerCase: number;\n /** Minimum number of approved cases required after the review gate. */\n minApprovedCases: number;\n /** Validation pipeline must not block. */\n requirePolicyPass: boolean;\n /** Visual sidecar gate must not block (when sidecar is present). */\n requireVisualSidecarPass: boolean;\n /**\n * Optional minimum job-level self-verify rubric score in `[0, 1]`\n * (Issue #1379). When set, the eval gate fails the run if the rubric\n * pass produced a `jobLevelRubricScore` strictly below this threshold.\n * When omitted, the rubric job-level score is informational only.\n */\n minJobRubricScore?: number;\n /**\n * When `true`, the eval gate also fails when the self-verify rubric\n * pass attached a `refusal` to its report. Defaulted to `false` so\n * the eval gate stays byte-stable for fixtures that do not exercise\n * the opt-in pass.\n */\n requireRubricPass?: boolean;\n}\n\n/** Failure record describing a single threshold breach. */\nexport interface Wave1ValidationEvalFailure {\n rule:\n | \"min_trace_coverage_fields\"\n | \"min_trace_coverage_actions\"\n | \"min_trace_coverage_validations\"\n | \"min_qc_mapping_exportable_fraction\"\n | \"max_duplicate_similarity\"\n | \"min_expected_results_per_case\"\n | \"min_approved_cases\"\n | \"policy_blocked\"\n | \"visual_sidecar_blocked\"\n | \"validation_blocked\"\n | \"export_refused\"\n | \"min_job_rubric_score\"\n | \"rubric_pass_refused\";\n /** Numeric or boolean observed value (encoded as number for comparators). */\n actual: number;\n /** Numeric or boolean threshold that was breached. */\n threshold: number;\n message: string;\n}\n\n/** Per-fixture metrics computed by the Wave 1 Validation evaluation gate. */\nexport interface Wave1ValidationEvalFixtureMetrics {\n fixtureId: string;\n totalGeneratedCases: number;\n approvedCases: number;\n blockedCases: number;\n needsReviewCases: number;\n detectedFields: number;\n coveredFields: number;\n detectedActions: number;\n coveredActions: number;\n detectedValidations: number;\n coveredValidations: number;\n exportableApprovedCases: number;\n maxObservedDuplicateSimilarity: number;\n minObservedExpectedResultsPerCase: number;\n policyBlocked: boolean;\n validationBlocked: boolean;\n visualSidecarBlocked: boolean;\n exportRefused: boolean;\n /**\n * Optional job-level self-verify rubric score (Issue #1379). Only\n * present when the rubric pass ran for the fixture. Mirrors the value\n * stored on `coverage-report.json#rubricScore` (rounded to 6 digits).\n */\n jobRubricScore?: number;\n /**\n * Whether the rubric pass attached a `refusal` to its report\n * (Issue #1379). `true` when the LLM gateway refused, the response\n * failed schema validation, or the per-case score set was incomplete.\n */\n rubricRefused?: boolean;\n}\n\n/** Per-fixture evaluation outcome. */\nexport interface Wave1ValidationEvalFixtureReport {\n fixtureId: string;\n pass: boolean;\n metrics: Wave1ValidationEvalFixtureMetrics;\n failures: Wave1ValidationEvalFailure[];\n}\n\n/**\n * Aggregate evaluation report covering one or more fixtures. This artifact\n * is byte-stable: fixtures and failures are sorted, hashes are not embedded,\n * and timestamps are caller-provided.\n */\nexport interface Wave1ValidationEvalReport {\n schemaVersion: typeof WAVE1_VALIDATION_EVAL_REPORT_SCHEMA_VERSION;\n contractVersion: string;\n testIntelligenceContractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n generatedAt: string;\n thresholds: Wave1ValidationEvalThresholds;\n fixtures: Wave1ValidationEvalFixtureReport[];\n pass: boolean;\n}\n\n/* ------------------------------------------------------------------ */\n/* Wave 1 Validation in-toto attestation + Sigstore signing (Issue #1377) */\n/* ------------------------------------------------------------------ */\n\n/**\n * Schema version for the in-toto v1 attestation envelope produced per\n * job by the Wave 1 Validation harness. Bumped on any breaking change to the\n * statement payload, predicate shape, or DSSE encoding.\n */\nexport const WAVE1_VALIDATION_ATTESTATION_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** in-toto v1 statement type URI. */\nexport const WAVE1_VALIDATION_ATTESTATION_STATEMENT_TYPE =\n \"https://in-toto.io/Statement/v1\" as const;\n\n/**\n * Predicate type URI identifying the Wave 1 Validation evidence shape. Bumped\n * in lockstep with the schema version when the predicate fields change.\n */\nexport const WAVE1_VALIDATION_ATTESTATION_PREDICATE_TYPE =\n \"https://workspace-dev.figmapipe.dev/test-intelligence/wave1-validation-evidence/v1\" as const;\n\n/**\n * DSSE `payloadType` stamped onto every in-toto attestation. The pre-\n * authentication encoding (PAE) hashes this value alongside the payload\n * bytes so it is bound to the signature.\n */\nexport const WAVE1_VALIDATION_ATTESTATION_PAYLOAD_TYPE =\n \"application/vnd.in-toto+json\" as const;\n\n/** Filename of the persisted in-toto DSSE envelope. */\nexport const WAVE1_VALIDATION_ATTESTATION_ARTIFACT_FILENAME =\n \"wave1-validation-attestation.intoto.json\" as const;\n\n/** Filename of the persisted Sigstore bundle when signing is enabled. */\nexport const WAVE1_VALIDATION_ATTESTATION_BUNDLE_FILENAME =\n \"wave1-validation-attestation.bundle.json\" as const;\n\n/** Subdirectory under a run dir where attestation envelopes are persisted. */\nexport const WAVE1_VALIDATION_ATTESTATIONS_DIRECTORY =\n \"evidence/attestations\" as const;\n\n/** Subdirectory under a run dir where Sigstore signature bundles are persisted. */\nexport const WAVE1_VALIDATION_SIGNATURES_DIRECTORY =\n \"evidence/signatures\" as const;\n\n/** Sigstore bundle media type — pinned to the v0.3 envelope shape. */\nexport const WAVE1_VALIDATION_ATTESTATION_BUNDLE_MEDIA_TYPE =\n \"application/vnd.dev.sigstore.bundle.v0.3+json\" as const;\n\n/**\n * Allowed signing modes for the Wave 1 Validation attestation.\n *\n * - `unsigned` (default) — emit DSSE envelope with empty `signatures`,\n * no Sigstore bundle. Always works air-gapped without network access.\n * - `sigstore` — emit DSSE envelope with one or more signatures and a\n * Sigstore bundle alongside. The signer is operator-supplied; the\n * built-in key-bound signer uses ECDSA P-256 from `node:crypto` so\n * tests and verifiers run without external network calls. A keyless\n * flow (Fulcio + Rekor) plugs into the same signer interface but is\n * never invoked by default.\n */\nexport const ALLOWED_WAVE1_VALIDATION_ATTESTATION_SIGNING_MODES = [\n \"unsigned\",\n \"sigstore\",\n] as const;\n\n/** Discriminant of the active signing mode. */\nexport type Wave1ValidationAttestationSigningMode =\n (typeof ALLOWED_WAVE1_VALIDATION_ATTESTATION_SIGNING_MODES)[number];\n\n/** Subject record inside the in-toto v1 statement. */\nexport interface Wave1ValidationAttestationSubject {\n /** Relative artifact path inside the run directory (no leading slash). */\n name: string;\n /** Subject digest map. Always populated with at least `sha256`. */\n digest: { sha256: string };\n}\n\n/**\n * Visual-sidecar identity carried into the attestation predicate so an\n * auditor can verify the multimodal chain of custody (Issue #1386\n * addendum to #1377). Mirrors the fields already attested on the\n * evidence manifest but pinned to the predicate version.\n */\nexport interface Wave1ValidationAttestationVisualSidecarIdentity {\n selectedDeployment: SidecarDeployment;\n fallbackReason: VisualSidecarFallbackReason;\n visualPrimary?: SidecarDeployment | \"none\";\n visualFallback?: SidecarDeployment | \"none\";\n resultArtifactSha256: string;\n}\n\n/**\n * Predicate body of the Wave 1 Validation attestation. The predicate carries\n * pipeline-identity facts (model deployments, prompt template, schema,\n * policy, export profile) plus the manifest's own SHA-256 so the\n * statement attests both the artifact subjects and the metadata\n * envelope used to produce them. No secrets, prompts, or response\n * bodies are embedded — only identity hashes and version stamps.\n */\nexport interface Wave1ValidationAttestationPredicate {\n schemaVersion: typeof WAVE1_VALIDATION_ATTESTATION_SCHEMA_VERSION;\n contractVersion: string;\n testIntelligenceContractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n fixtureId: string;\n jobId: string;\n generatedAt: string;\n /** Versions stamped by the harness at run time. */\n promptTemplateVersion: typeof TEST_INTELLIGENCE_PROMPT_TEMPLATE_VERSION;\n generatedTestCaseSchemaVersion: typeof GENERATED_TEST_CASE_SCHEMA_VERSION;\n visualSidecarSchemaVersion: typeof VISUAL_SIDECAR_SCHEMA_VERSION;\n redactionPolicyVersion: typeof REDACTION_POLICY_VERSION;\n /** Policy bundle identity (validation gate). */\n policyProfileId: string;\n policyProfileVersion: string;\n /** Export profile identity (export-only QC pipeline). */\n exportProfileId: string;\n exportProfileVersion: string;\n /** Replay-cache identity hashes. */\n promptHash: string;\n schemaHash: string;\n inputHash: string;\n cacheKeyDigest: string;\n /** Identity of every model role active during the run. */\n modelDeployments: {\n testGeneration: string;\n visualPrimary?: SidecarDeployment | \"none\";\n visualFallback?: SidecarDeployment | \"none\";\n };\n /** Visual-sidecar chain-of-custody identity (when present). */\n visualSidecar?: Wave1ValidationAttestationVisualSidecarIdentity;\n /** Active signing mode; mirrored from the run input for auditability. */\n signingMode: Wave1ValidationAttestationSigningMode;\n /** SHA-256 of the canonical evidence manifest the attestation covers. */\n manifestSha256: string;\n /**\n * SHA-256 of the canonical per-source FinOps breakdown embedded in the\n * FinOps report. Present when the run emitted `finops/budget-report.json`.\n */\n bySourceHash?: string;\n /** Filename of the manifest artifact (relative to the run dir). */\n manifestFilename: typeof WAVE1_VALIDATION_EVIDENCE_MANIFEST_ARTIFACT_FILENAME;\n /** Hard invariant — no raw screenshot bytes attested. */\n rawScreenshotsIncluded: false;\n /** Hard invariant — no API keys / bearer tokens attested. */\n secretsIncluded: false;\n /** Hard invariant — test_generation never received an image payload. */\n imagePayloadSentToTestGeneration: false;\n}\n\n/** in-toto v1 statement envelope (the DSSE payload after base64 decode). */\nexport interface Wave1ValidationAttestationStatement {\n _type: typeof WAVE1_VALIDATION_ATTESTATION_STATEMENT_TYPE;\n predicateType: typeof WAVE1_VALIDATION_ATTESTATION_PREDICATE_TYPE;\n /** Sorted-by-name, de-duplicated subject list. */\n subject: Wave1ValidationAttestationSubject[];\n predicate: Wave1ValidationAttestationPredicate;\n}\n\n/** A single signature attached to a DSSE envelope. */\nexport interface Wave1ValidationAttestationSignature {\n /** Stable, non-secret identifier for the signing key. */\n keyid: string;\n /** Base64 (RFC 4648 §4) encoded signature bytes. */\n sig: string;\n}\n\n/**\n * DSSE envelope (canonical form). When `signatures` is empty the\n * envelope represents an unsigned attestation. When populated, each\n * signature is an ECDSA P-256 signature over the PAE-encoded\n * (payloadType, payload) tuple, base64-encoded into `sig`.\n */\nexport interface Wave1ValidationAttestationDsseEnvelope {\n /** Base64 (RFC 4648 §4) encoded `Wave1ValidationAttestationStatement` JSON. */\n payload: string;\n payloadType: typeof WAVE1_VALIDATION_ATTESTATION_PAYLOAD_TYPE;\n signatures: Wave1ValidationAttestationSignature[];\n}\n\n/**\n * Public-key verification material. Used by the key-bound Sigstore\n * signing flow (and by air-gapped verifiers that pin a single signer\n * key). The PEM-encoded public key MUST be a SubjectPublicKeyInfo over\n * the prime256v1 (P-256) curve.\n */\nexport interface Wave1ValidationAttestationPublicKeyMaterial {\n /** Stable, non-secret signer reference (matches `Wave1ValidationAttestationSignature.keyid`). */\n hint: string;\n /** PEM-encoded SubjectPublicKeyInfo for the matching public key. */\n publicKeyPem: string;\n /** Signing algorithm used to produce the DSSE signatures. */\n algorithm: \"ecdsa-p256-sha256\";\n}\n\n/**\n * X.509 certificate-chain verification material. Used by the Sigstore\n * keyless signing flow: the leaf certificate carries the OIDC-bound\n * subject identity and is signed by Fulcio. Verifiers reconstruct the\n * public key from the leaf certificate, then validate it through the\n * chain to a trust root the operator pins.\n *\n * The repo does not vendor Fulcio root certificates — operators wire\n * the trust root themselves. The cert-chain shape is provided here as\n * a load-bearing type so the Sigstore bundle media type can carry\n * keyless signatures end-to-end without breaking changes.\n */\nexport interface Wave1ValidationAttestationCertificateChainMaterial {\n /** Stable, non-secret signer reference (matches `Wave1ValidationAttestationSignature.keyid`). */\n hint: string;\n /**\n * PEM-encoded certificate chain, leaf first. The leaf certificate's\n * subject public key is used to verify the DSSE signature. Operators\n * wiring full Sigstore keyless flow include the Fulcio-issued leaf\n * (with the OIDC subject as a SAN extension) and any intermediate(s)\n * up to a trust root.\n */\n certificateChainPem: string;\n /** Signing algorithm used to produce the DSSE signatures. */\n algorithm: \"ecdsa-p256-sha256\";\n /**\n * Optional Rekor transparency-log inclusion proof reference. When\n * present, a verifier MAY consult its trusted Rekor instance to\n * confirm the entry is logged. The repo never fetches Rekor by\n * default; the field is opaque metadata.\n */\n rekorLogIndex?: number;\n}\n\n/**\n * Sigstore bundle verification material. Discriminated by which form\n * the operator wires: `publicKey` for key-bound signing (the repo's\n * default), `x509CertificateChain` for keyless signing (operator-\n * supplied integration with Fulcio + Rekor).\n */\nexport type Wave1ValidationAttestationVerificationMaterial =\n | { publicKey: Wave1ValidationAttestationPublicKeyMaterial }\n | {\n x509CertificateChain: Wave1ValidationAttestationCertificateChainMaterial;\n };\n\n/** Sigstore-shaped bundle persisted alongside a signed attestation. */\nexport interface Wave1ValidationAttestationBundle {\n mediaType: typeof WAVE1_VALIDATION_ATTESTATION_BUNDLE_MEDIA_TYPE;\n /**\n * The DSSE envelope this bundle witnesses. Identical bytes to the\n * `evidence/attestations/...` artifact; duplication is intentional so\n * the bundle is self-contained.\n */\n dsseEnvelope: Wave1ValidationAttestationDsseEnvelope;\n /** Verification material — public key OR x509 certificate chain. */\n verificationMaterial: Wave1ValidationAttestationVerificationMaterial;\n}\n\n/**\n * Audit-timeline summary surfaced on the harness result. Carries only\n * non-secret identifiers and digests so callers can render signing\n * provenance without re-reading on-disk artifacts.\n */\nexport interface Wave1ValidationAttestationSummary {\n signingMode: Wave1ValidationAttestationSigningMode;\n /** Stable signer identifier (matches `keyid`). `undefined` when unsigned. */\n signerReference?: string;\n /** Relative path of the persisted in-toto envelope. */\n attestationFilename: string;\n /** SHA-256 of the canonical envelope bytes. */\n attestationSha256: string;\n /** Relative path of the Sigstore bundle. `undefined` when unsigned. */\n bundleFilename?: string;\n /** SHA-256 of the canonical bundle bytes. `undefined` when unsigned. */\n bundleSha256?: string;\n}\n\n/**\n * Failure record produced by `verifyWave1ValidationAttestation`. Each failure\n * names the specific subject / signature / metadata field that failed\n * so an auditor can pinpoint the mismatch without re-running the\n * harness.\n */\nexport interface Wave1ValidationAttestationVerificationFailure {\n /** Stable failure code. */\n code:\n | \"envelope_unparseable\"\n | \"envelope_payload_type_mismatch\"\n | \"envelope_payload_decode_failed\"\n | \"statement_unparseable\"\n | \"statement_type_mismatch\"\n | \"statement_predicate_type_mismatch\"\n | \"statement_predicate_invalid\"\n | \"subject_missing_artifact\"\n | \"subject_digest_mismatch\"\n | \"subject_unattested_artifact\"\n | \"signing_mode_mismatch\"\n | \"signature_required\"\n | \"signature_unsigned_envelope_carries_signatures\"\n | \"signature_invalid_keyid\"\n | \"signature_invalid_encoding\"\n | \"signature_unverified\"\n | \"bundle_missing\"\n | \"bundle_envelope_mismatch\"\n | \"bundle_public_key_missing\"\n | \"manifest_sha256_mismatch\"\n | \"bySource_hash_mismatch\";\n /** Subject / artifact / field that triggered the failure. */\n reference: string;\n /** Human-readable diagnostic. Never includes secrets. */\n message: string;\n}\n\n/** Result of `verifyWave1ValidationAttestation`. */\nexport interface Wave1ValidationAttestationVerificationResult {\n ok: boolean;\n signingMode: Wave1ValidationAttestationSigningMode;\n /** Number of signatures present (0 for unsigned). */\n signatureCount: number;\n /** True iff every present signature verified against `publicKey`. */\n signaturesVerified: boolean;\n /** Structured failure list — empty when `ok === true`. */\n failures: Wave1ValidationAttestationVerificationFailure[];\n}\n\n/* ------------------------------------------------------------------ */\n/* QC adapter + dry-run report (Issue #1368) */\n/* ------------------------------------------------------------------ */\n\n/** Schema version for the persisted dry-run report artifact (Issue #1368). */\nexport const DRY_RUN_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted dry-run report artifact. */\nexport const DRY_RUN_REPORT_ARTIFACT_FILENAME = \"dry-run-report.json\" as const;\n\n/**\n * Allowed transfer modes recognised by the QC adapter façade.\n *\n * - `export_only` — produce on-disk artifacts; no QC API touched.\n * - `dry_run` — validate target mapping (folder, fields, schema) without\n * creating tests in the QC tool.\n * - `api_transfer` — controlled OpenText ALM write path implemented by the\n * Wave 3 transfer orchestrator. The dry-run adapter still throws\n * `mode_not_implemented` when called directly with this mode.\n */\nexport const ALLOWED_QC_ADAPTER_MODES = [\n \"export_only\",\n \"dry_run\",\n \"api_transfer\",\n] as const;\nexport type QcAdapterMode = (typeof ALLOWED_QC_ADAPTER_MODES)[number];\n\n/**\n * Allowed QC adapter provider discriminators. Wave 2 ships `opentext_alm`;\n * the rest are stub identifiers reserved so future adapters plug in\n * without contract churn.\n */\nexport const ALLOWED_QC_ADAPTER_PROVIDERS = [\n \"opentext_alm\",\n \"opentext_octane\",\n \"opentext_valueedge\",\n \"xray\",\n \"testrail\",\n \"azure_devops_test_plans\",\n \"qtest\",\n \"custom\",\n] as const;\nexport type QcAdapterProvider = (typeof ALLOWED_QC_ADAPTER_PROVIDERS)[number];\n\n/**\n * Allowed mapping-profile validation issue codes (Issue #1368). Tracks the\n * `ValidationIssue[]` style used elsewhere in test-intelligence.\n */\nexport const ALLOWED_QC_MAPPING_PROFILE_ISSUE_CODES = [\n \"missing_base_url_alias\",\n \"invalid_base_url_alias\",\n \"missing_domain\",\n \"missing_project\",\n \"missing_target_folder_path\",\n \"invalid_target_folder_path\",\n \"missing_test_entity_type\",\n \"unsupported_test_entity_type\",\n \"missing_required_fields\",\n \"duplicate_required_field\",\n \"missing_design_step_mapping\",\n \"design_step_mapping_field_invalid\",\n \"credential_like_field_present\",\n \"provider_mismatch\",\n \"profile_id_mismatch\",\n] as const;\nexport type QcMappingProfileIssueCode =\n (typeof ALLOWED_QC_MAPPING_PROFILE_ISSUE_CODES)[number];\n\n/**\n * Allowed reasons the QC adapter may refuse to produce a dry-run report.\n *\n * `provider_not_implemented` (Issue #1374) is appended at the end so the\n * ordinal positions of prior codes stay byte-stable for callers that pin\n * to a known index. The code is emitted by the dry-run-only stub adapter\n * for non-ALM providers that have no real implementation yet.\n */\nexport const ALLOWED_DRY_RUN_REFUSAL_CODES = [\n \"no_mapped_test_cases\",\n \"mapping_profile_invalid\",\n \"provider_mismatch\",\n \"mode_not_implemented\",\n \"folder_resolution_failed\",\n \"provider_not_implemented\",\n] as const;\nexport type DryRunRefusalCode = (typeof ALLOWED_DRY_RUN_REFUSAL_CODES)[number];\n\n/**\n * Allowed QC provider operations (Issue #1374).\n *\n * Each builtin provider descriptor advertises which of these operations its\n * adapter implements. The registry uses the matrix to surface \"what does\n * this provider support\" without coupling to a concrete adapter:\n *\n * - `validate_profile` — pure structural validator runs against the\n * supplied mapping profile.\n * - `resolve_target_folder` — adapter knows how to validate a target\n * folder path against its provider (read-only resolver).\n * - `dry_run` — adapter can produce a `DryRunReportArtifact` (potentially\n * a fail-closed stub).\n * - `export_only` — adapter can emit export-only artifacts.\n * - `api_transfer` — adapter can perform controlled API writes.\n * - `register_custom` — caller may register a custom adapter under this\n * provider id (only true for the reserved `custom` slot).\n */\nexport const ALLOWED_QC_PROVIDER_OPERATIONS = [\n \"validate_profile\",\n \"resolve_target_folder\",\n \"dry_run\",\n \"export_only\",\n \"api_transfer\",\n \"register_custom\",\n] as const;\nexport type QcProviderOperation =\n (typeof ALLOWED_QC_PROVIDER_OPERATIONS)[number];\n\n/** Allowed states of a target-folder resolution attempt under `dry_run`. */\nexport const ALLOWED_DRY_RUN_FOLDER_RESOLUTION_STATES = [\n \"resolved\",\n \"missing\",\n \"simulated\",\n \"invalid_path\",\n] as const;\nexport type DryRunFolderResolutionState =\n (typeof ALLOWED_DRY_RUN_FOLDER_RESOLUTION_STATES)[number];\n\n/** Provider-neutral mapping profile shape consumed by all QC adapters. */\nexport interface QcMappingProfile {\n /** Profile identity (e.g. `opentext-alm-default`). */\n id: string;\n version: string;\n /** QC provider this profile targets. */\n provider: QcAdapterProvider;\n /**\n * Symbolic alias for the base URL of the target QC tenant. Adapters\n * resolve the actual URL from operator-supplied secrets at call time;\n * the alias never carries credentials and never embeds userinfo.\n */\n baseUrlAlias: string;\n /** Tenant domain (e.g. `DEFAULT`). */\n domain: string;\n /** Tenant project (e.g. `payments-checkout`). */\n project: string;\n /** Forward-slash-separated `/Subject/...` folder path used as default root. */\n targetFolderPath: string;\n /** Test entity type string accepted by the QC tool (e.g. `MANUAL`). */\n testEntityType: string;\n /** Required field names enforced on each mapped case. Sorted, deduped. */\n requiredFields: string[];\n /**\n * Per-design-step field mapping. The keys are the GeneratedTestCaseStep\n * fields that participate in the QC step entity (`action`, `expected`,\n * `data`); the values are the QC field names they map to.\n */\n designStepMapping: {\n action: string;\n expected: string;\n data?: string;\n };\n}\n\n/** Single mapping-profile validation issue. */\nexport interface QcMappingProfileIssue {\n path: string;\n code: QcMappingProfileIssueCode;\n severity: TestCaseValidationSeverity;\n message: string;\n}\n\n/** Aggregate mapping-profile validation result. */\nexport interface QcMappingProfileValidationResult {\n ok: boolean;\n errorCount: number;\n warningCount: number;\n issues: QcMappingProfileIssue[];\n}\n\n/**\n * Capability matrix for a QC provider (Issue #1374).\n *\n * Each flag mirrors a `QcProviderOperation`. Wave 3 ships only the\n * `opentext_alm` provider with the full matrix `true`; the other six\n * builtin providers advertise dry-run + validate only and refuse writes.\n * The reserved `custom` slot is published with every flag `false` until a\n * caller registers a concrete adapter.\n *\n * The shape is a closed product type so a future operation cannot be\n * silently introduced without a contract bump — every consumer reading the\n * matrix today is guaranteed to see exactly these six fields.\n */\nexport interface QcProviderCapabilities {\n /** Adapter exposes a structural `validateProfile` pass. */\n validateProfile: boolean;\n /** Adapter knows how to validate a target folder path (read-only). */\n resolveTargetFolder: boolean;\n /** Adapter can emit a `DryRunReportArtifact` (concrete or fail-closed stub). */\n dryRun: boolean;\n /** Adapter can emit export-only artifacts (CSV/XLSX/XML). */\n exportOnly: boolean;\n /** Adapter can perform controlled API writes against the live tool. */\n apiTransfer: boolean;\n /** Caller may register a concrete custom adapter under this provider id. */\n registerCustom: boolean;\n}\n\n/**\n * Descriptor for a builtin or custom-registered QC provider (Issue #1374).\n *\n * Descriptors are returned by the registry so a UI or operator audit can\n * answer \"which providers are wired up and what can they do?\" without\n * loading any adapter implementation. They carry no credentials, no URLs,\n * and no runtime mutable state.\n */\nexport interface QcProviderDescriptor {\n /** Provider discriminator from `ALLOWED_QC_ADAPTER_PROVIDERS`. */\n provider: QcAdapterProvider;\n /** Short human-readable label, e.g. `\"OpenText ALM\"`. */\n label: string;\n /** Semver-shaped descriptor version, bumped when the matrix changes. */\n version: string;\n /** True for the eight in-tree descriptors; false for caller-registered slots. */\n builtin: boolean;\n /** Capability matrix advertised for this provider. */\n capabilities: QcProviderCapabilities;\n /**\n * Optional pointer to the mapping-profile factory id a caller can use to\n * seed a fresh profile (e.g. `opentext-alm-default`). Absent when the\n * provider has no in-tree default profile yet.\n */\n mappingProfileSeedId?: string;\n}\n\n/** Per-test-case completeness row inside the dry-run report. */\nexport interface DryRunMappingCompletenessEntry {\n testCaseId: string;\n externalIdCandidate: string;\n /** Required field names whose mapped value was missing on the case. */\n missingRequiredFields: string[];\n /** True when every required field is populated AND the entry is exportable. */\n complete: boolean;\n}\n\n/** Aggregate mapping completeness summary. */\nexport interface DryRunMappingCompletenessSummary {\n totalCases: number;\n completeCases: number;\n incompleteCases: number;\n /** Distinct missing-field names across all cases, sorted. */\n missingFieldsAcrossCases: string[];\n perCase: DryRunMappingCompletenessEntry[];\n}\n\n/** Outcome of attempting to resolve a target folder under `dry_run`. */\nexport interface DryRunFolderResolution {\n state: DryRunFolderResolutionState;\n path: string;\n /**\n * Free-form, redacted evidence string supplied by the resolver (e.g.\n * `\"simulated:matched-segments=3\"`). Never includes a URL or token.\n */\n evidence: string;\n}\n\n/** Per-test-case planned ALM entity payload preview (REDACTED). */\nexport interface DryRunPlannedEntityPayload {\n testCaseId: string;\n externalIdCandidate: string;\n testEntityType: string;\n targetFolderPath: string;\n /** Mapped QC fields (deterministic, redacted, no credentials). */\n fields: { name: string; value: string }[];\n /** Number of design steps in the planned payload. */\n designStepCount: number;\n /**\n * Mean visual-sidecar confidence (0..1) across matching screen records,\n * rounded to 4 decimals for byte-stability. Issue #1374 multimodal\n * addendum (2026-04-24). Absent (no key set) when the case has no\n * matching visual records or when emitted by the dry-run stub adapter.\n */\n visualConfidence?: number;\n /**\n * Sorted, de-duplicated set of non-`ok` outcome codes contributing to the\n * matching visual records. Surfaces ambiguity reasons (`low_confidence`,\n * `schema_invalid`, etc.) without re-emitting the raw issue text. Absent\n * when no records match. Issue #1374.\n */\n visualAmbiguityFlags?: VisualSidecarValidationOutcome[];\n /**\n * True when at least one matching visual record carries the\n * `fallback_used` outcome — i.e. the secondary multimodal deployment\n * produced the description the case relies on. Absent when no records\n * match. Issue #1374.\n */\n visualFallbackUsed?: boolean;\n /**\n * Sorted by `screenId`, `modelDeployment`, then `evidenceHash`.\n * Each ref carries a derivative identity hash that lets a reviewer\n * correlate the planned payload back to the per-screen validation record\n * without re-importing raw screenshot bytes. Absent when no records match.\n * Issue #1374.\n *\n * Field semantics:\n * - `screenId` — matching `VisualSidecarValidationRecord.screenId`.\n * - `modelDeployment` — sourced verbatim from\n * `VisualSidecarValidationRecord.deployment`. The contract historically\n * uses the field name `deployment` on the record; this ref re-exposes\n * it under `modelDeployment` to align with the broader replay-cache\n * idiom (see `SelfVerifyRubricReplayCacheKey.modelDeployment`).\n * - `evidenceHash` — `sha256` hex of the canonical validation-record\n * identity tuple `(screenId|deployment|sortedOutcomes|roundedConfidence)`.\n * Note this is NOT a hash of screenshot bytes — the dry-run adapter\n * never receives image bytes. The `VisualSidecarCaptureIdentity.sha256`\n * (image-byte hash) lives only on the upstream\n * `VisualSidecarSuccess` artifact, which dry-run does not consume.\n */\n visualEvidenceRefs?: {\n screenId: string;\n modelDeployment: string;\n evidenceHash: string;\n }[];\n}\n\n/**\n * Visual evidence flag attached to a mapped case when the case's mapping\n * derives from low-confidence visual-only sidecar observations (Issue\n * #1386 / #1368).\n */\nexport interface DryRunVisualEvidenceFlag {\n testCaseId: string;\n /** Originating screen ids in the visual sidecar that drive the flag. */\n screenIds: string[];\n /** Mean sidecar confidence across the matching screen records (0..1). */\n sidecarConfidence: number;\n /** Per-screen ambiguity outcome counts contributing to the flag. */\n ambiguityFlags: VisualSidecarValidationOutcome[];\n /** Stable trace references — figmaTraceRefs subset that drove the mapping. */\n traceRefs: GeneratedTestCaseFigmaTrace[];\n /**\n * Explicit reason classification:\n * - `visual_only_low_confidence_mapping` — mapping derives only from\n * sidecar observations whose confidence is below the configured\n * threshold; reviewer must validate before transfer.\n */\n reason: \"visual_only_low_confidence_mapping\";\n}\n\n/** Aggregate dry-run report artifact. */\nexport interface DryRunReportArtifact {\n schemaVersion: typeof DRY_RUN_REPORT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n /** Deterministic id derived from job + adapter + profile + clock. */\n reportId: string;\n jobId: string;\n generatedAt: string;\n mode: QcAdapterMode;\n adapter: { provider: QcAdapterProvider; version: string };\n profile: { id: string; version: string };\n /** True iff the adapter refused to produce a usable report. */\n refused: boolean;\n refusalCodes: DryRunRefusalCode[];\n profileValidation: QcMappingProfileValidationResult;\n completeness: DryRunMappingCompletenessSummary;\n folderResolution: DryRunFolderResolution;\n /** Sorted by `testCaseId`. Empty when the report is refused. */\n plannedPayloads: DryRunPlannedEntityPayload[];\n /** Sorted by `testCaseId`. */\n visualEvidenceFlags: DryRunVisualEvidenceFlag[];\n /** Hard invariant: raw screenshots are never embedded into dry-run payloads. */\n rawScreenshotsIncluded: false;\n /** Hard invariant: credentials are never embedded into dry-run payloads. */\n credentialsIncluded: false;\n}\n\n/* ------------------------------------------------------------------ */\n/* QC adapter API transfer report (Issue #1372 — Wave 3) */\n/* ------------------------------------------------------------------ */\n\n/**\n * Controlled OpenText ALM API transfer surface (Issue #1372).\n *\n * Wave 3 introduces the production-capable `api_transfer` mode for the\n * QC adapter. Unlike `dry_run` (no I/O) or `export_only` (artifact write\n * only), `api_transfer` performs real writes against an OpenText ALM\n * tenant — but only after every Wave 1/2 gate has been satisfied:\n *\n * 1. Feature gate (`TEST_INTELLIGENCE_ENABLED=1`).\n * 2. Admin/startup gate (`allowApiTransfer=true`).\n * 3. Bearer token configured + accepted on the server-side caller.\n * 4. Test cases reached `approved` (or already `exported`/`transferred`).\n * 5. Policy decisions are not `blocked`.\n * 6. Dry-run report for the same profile reports `refused: false`.\n * 7. Visual-sidecar evidence present for visual-driven cases (#1386).\n * 8. Four-eyes approval recorded when enforced (#1376).\n *\n * Transfer is idempotent: re-running on the same approved set never\n * creates duplicates (lookup by `externalIdCandidate` + `targetFolderPath`).\n *\n * Hard invariants stamped at the type level on every emitted artifact:\n * - `rawScreenshotsIncluded: false`\n * - `credentialsIncluded: false`\n * - `transferUrlIncluded: false`\n */\n\n/** Schema version for the persisted transfer-report artifact (Issue #1372). */\nexport const TRANSFER_REPORT_SCHEMA_VERSION = \"1.1.0\" as const;\n\n/** Canonical filename for the persisted transfer-report artifact. */\nexport const TRANSFER_REPORT_ARTIFACT_FILENAME =\n \"transfer-report.json\" as const;\n\n/** Schema version for the persisted qc-created-entities artifact (Issue #1372). */\nexport const QC_CREATED_ENTITIES_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted qc-created-entities artifact. */\nexport const QC_CREATED_ENTITIES_ARTIFACT_FILENAME =\n \"qc-created-entities.json\" as const;\n\n/**\n * Allowed reasons the QC adapter may refuse to perform an API transfer.\n *\n * These are evaluated in fail-closed order — the first refusal stops the\n * pipeline before any state-mutating call leaves the process. The\n * `transfer-report.json` artifact records every refusal that fired so\n * the operator can address them all in one cycle.\n */\nexport const ALLOWED_TRANSFER_REFUSAL_CODES = [\n \"feature_disabled\",\n \"admin_gate_disabled\",\n \"bearer_token_missing\",\n \"mapping_profile_invalid\",\n \"provider_mismatch\",\n \"no_mapped_test_cases\",\n \"no_approved_test_cases\",\n \"unapproved_test_cases_present\",\n \"policy_blocked_cases_present\",\n \"schema_invalid_cases_present\",\n \"visual_sidecar_blocked\",\n \"visual_sidecar_evidence_missing\",\n \"review_state_inconsistent\",\n \"four_eyes_pending\",\n \"dry_run_refused\",\n \"dry_run_missing\",\n \"folder_resolution_failed\",\n \"mode_not_implemented\",\n] as const;\nexport type TransferRefusalCode =\n (typeof ALLOWED_TRANSFER_REFUSAL_CODES)[number];\n\n/**\n * Per-test-case outcome of an API transfer attempt. Discriminated so\n * report consumers can sort + count without re-deriving the state.\n *\n * - `created` — the entity did not exist; create call succeeded.\n * - `skipped_duplicate` — the entity already exists for this\n * `externalIdCandidate` + folder; no write performed.\n * - `failed` — adapter or transport error. When `qcEntityId` is\n * non-empty, the tenant may contain a partially created entity and the\n * rollback guidance must include it for operator cleanup.\n * - `refused` — pipeline-level refusal (e.g. unapproved); no call\n * was attempted.\n */\nexport const ALLOWED_TRANSFER_ENTITY_OUTCOMES = [\n \"created\",\n \"skipped_duplicate\",\n \"failed\",\n \"refused\",\n] as const;\nexport type TransferEntityOutcome =\n (typeof ALLOWED_TRANSFER_ENTITY_OUTCOMES)[number];\n\n/**\n * Allowed failure classes for a per-entity transfer failure. Mirrors the\n * gateway taxonomy so transport faults stay distinguishable from\n * server-side validation faults.\n */\nexport const ALLOWED_TRANSFER_FAILURE_CLASSES = [\n \"transport_error\",\n \"auth_failed\",\n \"permission_denied\",\n \"validation_rejected\",\n \"conflict_unresolved\",\n \"rate_limited\",\n \"server_error\",\n \"unknown\",\n] as const;\nexport type TransferFailureClass =\n (typeof ALLOWED_TRANSFER_FAILURE_CLASSES)[number];\n\n/** Hash-only evidence references that bind transfer to upstream artifacts. */\nexport interface TransferEvidenceReferences {\n /** SHA-256 hex of the QC mapping preview consumed by transfer. */\n qcMappingPreviewHash: string;\n /** SHA-256 hex of the dry-run report consumed by transfer. */\n dryRunReportHash: string;\n /** SHA-256 hex of the visual-sidecar validation report, when present. */\n visualSidecarReportHash: string;\n /** Sorted hash-only references to sidecar evidence used by mapped cases. */\n visualSidecarEvidenceHashes: string[];\n /** Optional SHA-256 hex of the generated test-case artifact. */\n generationOutputHash?: string;\n /** Optional SHA-256 hex of the reconciled intent IR artifact. */\n reconciledIntentIrHash?: string;\n}\n\n/** Audit metadata describing the operator/principal that authorised the run. */\nexport interface TransferAuditMetadata {\n /** Opaque actor handle; never an email or token. */\n actor: string;\n /** Stable id for the operator-supplied bearer-token principal. */\n authPrincipalId: string;\n /**\n * Whether the operator-supplied bearer token matched a configured\n * principal. `true` is required for the transfer to proceed.\n */\n bearerTokenAccepted: boolean;\n /** Reasons four-eyes review applied to one or more cases (sorted, deduped). */\n fourEyesReasons: FourEyesEnforcementReason[];\n /** Identity of the dry-run report consumed; binds the run to a validation. */\n dryRunReportId: string;\n /** Hash-only upstream artifact references; never raw prompts, screenshots, or credentials. */\n evidenceReferences: TransferEvidenceReferences;\n}\n\n/** Per-entity record inside the transfer report. */\nexport interface TransferEntityRecord {\n testCaseId: string;\n externalIdCandidate: string;\n targetFolderPath: string;\n outcome: TransferEntityOutcome;\n /**\n * Resolved QC entity id when the outcome is `created`, `skipped_duplicate`,\n * or a failed attempt left a partial tenant entity. Empty for `refused`.\n */\n qcEntityId: string;\n /** Number of design steps the adapter created for this entity. */\n designStepsCreated: number;\n /** Wall-clock timestamp at which the adapter recorded the outcome. */\n recordedAt: string;\n /** Failure class when `outcome === \"failed\"`; absent otherwise. */\n failureClass?: TransferFailureClass;\n /** Sanitised, length-bounded failure detail; never carries URLs/tokens. */\n failureDetail?: string;\n}\n\n/** Single created QC entity row in `qc-created-entities.json`. */\nexport interface QcCreatedEntity {\n testCaseId: string;\n externalIdCandidate: string;\n qcEntityId: string;\n /** Forward-slash-separated folder path under the profile root. */\n targetFolderPath: string;\n /** ISO-8601 UTC timestamp at which the entity was first created. */\n createdAt: string;\n /** Number of design steps persisted alongside the entity. */\n designStepCount: number;\n /**\n * `true` when the entity already existed on a prior transfer run for\n * the same `(externalIdCandidate, targetFolderPath)` tuple. Idempotent\n * re-runs preserve this flag so audit logs document the lineage.\n */\n preExisting: boolean;\n}\n\n/** Aggregate `qc-created-entities.json` artifact (Issue #1372). */\nexport interface QcCreatedEntitiesArtifact {\n schemaVersion: typeof QC_CREATED_ENTITIES_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n jobId: string;\n generatedAt: string;\n profileId: string;\n profileVersion: string;\n /** Sorted by `testCaseId` for deterministic emission. */\n entities: QcCreatedEntity[];\n /** Hard invariant: never carries the resolved transfer URL. */\n transferUrlIncluded: false;\n}\n\n/** Aggregate `transfer-report.json` artifact (Issue #1372). */\nexport interface TransferReportArtifact {\n schemaVersion: typeof TRANSFER_REPORT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n /** Deterministic id derived from job + adapter + profile + clock. */\n reportId: string;\n jobId: string;\n generatedAt: string;\n mode: QcAdapterMode;\n adapter: { provider: QcAdapterProvider; version: string };\n profile: { id: string; version: string };\n /** True iff the pipeline refused to perform any write. */\n refused: boolean;\n refusalCodes: TransferRefusalCode[];\n /** Sorted by `testCaseId`. Empty when refused before any attempt. */\n records: TransferEntityRecord[];\n /** Number of records whose outcome is `created`. */\n createdCount: number;\n /** Number of records whose outcome is `skipped_duplicate`. */\n skippedDuplicateCount: number;\n /** Number of records whose outcome is `failed`. */\n failedCount: number;\n /** Number of records whose outcome is `refused`. */\n refusedCount: number;\n /** Audit metadata for the run. */\n audit: TransferAuditMetadata;\n /** Hard invariant: raw screenshots are never embedded into transfer payloads. */\n rawScreenshotsIncluded: false;\n /** Hard invariant: credentials are never embedded into transfer payloads. */\n credentialsIncluded: false;\n /** Hard invariant: never carries the resolved transfer URL. */\n transferUrlIncluded: false;\n}\n\n/**\n * Jira Write Workflow contract surface (Issue #1482, Wave 5).\n *\n * Approved test cases may be written back to Jira as sub-tasks of a\n * specified parent issue. The pipeline is opt-in and fail-closed across\n * eight stacked gates (feature flag, admin gate, bearer token, valid\n * `parentIssueKey`, at least one approved case, no policy-blocked\n * cases, no schema-invalid cases, no visual-sidecar-blocked cases). All\n * gate violations are collected and reported in\n * `jira-write-report.json` so an operator can address them in one cycle.\n *\n * Idempotency is enforced via a stable `externalId` derived from the\n * `(jobId, testCaseId, parentIssueKey)` triple; lookups against the\n * tenant short-circuit duplicates to `skipped_duplicate`.\n *\n * Hard invariants stamped at the type level on every emitted artifact:\n * - `rawScreenshotsIncluded: false`\n * - `credentialsIncluded: false`\n *\n * Markdown artifacts are written separately (per test case) and never\n * embed bearer tokens, raw screenshots, or base64 image data.\n */\n\n/** Schema version for the persisted Jira write report artifact (Issue #1482). */\nexport const JIRA_WRITE_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the Jira write report artifact. */\nexport const JIRA_WRITE_REPORT_ARTIFACT_FILENAME =\n \"jira-write-report.json\" as const;\n\n/** Sub-directory under the run dir where Jira write artifacts are persisted. */\nexport const JIRA_WRITE_REPORT_ARTIFACT_DIRECTORY = \"jira-write\" as const;\n\n/** Schema version for the persisted Jira created sub-tasks artifact (Issue #1482). */\nexport const JIRA_CREATED_SUBTASKS_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the Jira created sub-tasks artifact. */\nexport const JIRA_CREATED_SUBTASKS_ARTIFACT_FILENAME =\n \"jira-created-subtasks.json\" as const;\n\n/**\n * Allowed Jira write modes. Only `jira_subtasks` is shipped in Wave 5;\n * the array is the source of truth so future modes plug in without\n * changing call sites.\n */\nexport const ALLOWED_JIRA_WRITE_MODE_VALUES = [\"jira_subtasks\"] as const;\nexport type JiraWriteMode = (typeof ALLOWED_JIRA_WRITE_MODE_VALUES)[number];\n\n/**\n * Allowed reasons the Jira write pipeline may refuse to perform any\n * sub-task creation. Evaluated in fail-closed order; every fired refusal\n * is recorded so operators can address them all in one cycle.\n */\nexport const ALLOWED_JIRA_WRITE_REFUSAL_CODES = [\n \"feature_gate_disabled\",\n \"admin_gate_disabled\",\n \"bearer_token_missing\",\n \"invalid_parent_issue_key\",\n \"no_approved_test_cases\",\n \"policy_blocked_cases_present\",\n \"schema_invalid_cases_present\",\n \"visual_sidecar_blocked\",\n] as const;\nexport type JiraWriteRefusalCode =\n (typeof ALLOWED_JIRA_WRITE_REFUSAL_CODES)[number];\n\n/**\n * Per-case outcome of a Jira sub-task write attempt.\n *\n * - `created` — sub-task did not exist; Jira create call succeeded.\n * - `skipped_duplicate` — sub-task already exists for this `externalId`\n * on the parent; no write performed.\n * - `failed` — adapter or transport error; pipeline continued with\n * subsequent cases (per-case failure isolation).\n * - `dry_run` — pipeline was invoked with `dryRun=true`; no Jira call\n * was attempted.\n */\nexport const ALLOWED_JIRA_WRITE_ENTITY_OUTCOMES = [\n \"created\",\n \"skipped_duplicate\",\n \"failed\",\n \"dry_run\",\n] as const;\nexport type JiraWriteEntityOutcome =\n (typeof ALLOWED_JIRA_WRITE_ENTITY_OUTCOMES)[number];\n\n/**\n * Allowed failure classes for a per-case Jira write failure. Mirrors the\n * Jira gateway taxonomy so transport faults stay distinguishable from\n * server-side validation faults.\n */\nexport const ALLOWED_JIRA_WRITE_FAILURE_CLASSES = [\n \"transport_error\",\n \"auth_failed\",\n \"permission_denied\",\n \"validation_rejected\",\n \"rate_limited\",\n \"server_error\",\n \"provider_not_implemented\",\n \"unknown\",\n] as const;\nexport type JiraWriteFailureClass =\n (typeof ALLOWED_JIRA_WRITE_FAILURE_CLASSES)[number];\n\n/**\n * Per-test-case sub-task record persisted in `jira-created-subtasks.json`\n * and embedded in the audit-shaped `jira-write-report.json`.\n */\nexport interface JiraSubTaskRecord {\n /** Generated test case identifier this sub-task corresponds to. */\n testCaseId: string;\n /** Stable idempotency key SHA-256(`jobId|testCaseId|parentIssueKey`). */\n externalId: string;\n outcome: JiraWriteEntityOutcome;\n /** Resolved Jira issue key for the created or pre-existing sub-task. */\n jiraIssueKey?: string;\n /** Failure classification when `outcome === \"failed\"`. */\n failureClass?: JiraWriteFailureClass;\n /**\n * Whether the failed sub-task attempt is safe to retry later. Present only\n * for failed outcomes so persisted status can distinguish transient\n * transport/rate-limit/server failures from permanent validation/auth faults.\n */\n retryable?: boolean;\n /** Sanitised, length-bounded failure detail; never carries URLs/tokens. */\n failureDetail?: string;\n}\n\n/** Aggregate `jira-created-subtasks.json` artifact (Issue #1482). */\nexport interface JiraCreatedSubtasksArtifact {\n schemaVersion: typeof JIRA_CREATED_SUBTASKS_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n jobId: string;\n parentIssueKey: string;\n generatedAt: string;\n /** Sorted by `testCaseId` for deterministic emission. */\n subtasks: JiraSubTaskRecord[];\n /** Hard invariant: raw screenshots are never embedded in Jira write payloads. */\n rawScreenshotsIncluded: false;\n /** Hard invariant: credentials are never embedded in Jira write payloads. */\n credentialsIncluded: false;\n}\n\n/** Audit metadata persisted alongside the Jira write report. */\nexport interface JiraWriteAuditMetadata {\n /** Stable opaque principal id; never an email or token. */\n principalId: string;\n /** Whether a bearer token was configured for the run. */\n bearerConfigured: boolean;\n /** Whether the admin gate (`allowJiraWrite`) was enabled. */\n adminEnabled: boolean;\n /** Whether the run was a dry-run (no live Jira calls). */\n dryRun: boolean;\n /** Mode used by this run; only `jira_subtasks` is shipped in Wave 5. */\n mode: JiraWriteMode;\n}\n\n/** Aggregate `jira-write-report.json` artifact (Issue #1482). */\nexport interface JiraWriteReportArtifact {\n schemaVersion: typeof JIRA_WRITE_REPORT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n jobId: string;\n parentIssueKey: string;\n generatedAt: string;\n /** True iff the pipeline refused to perform any write. */\n refused: boolean;\n /** Sorted, deduplicated refusal codes that fired. */\n refusalCodes: JiraWriteRefusalCode[];\n /** Total number of approved test cases supplied to the pipeline. */\n totalCases: number;\n /** Number of records whose outcome is `created`. */\n createdCount: number;\n /** Number of records whose outcome is `skipped_duplicate`. */\n skippedDuplicateCount: number;\n /** Number of records whose outcome is `failed`. */\n failedCount: number;\n /** Number of records whose outcome is `dry_run`. */\n dryRunCount: number;\n /** Audit metadata for the run. */\n audit: JiraWriteAuditMetadata;\n /** Hard invariant: raw screenshots are never embedded in Jira write payloads. */\n rawScreenshotsIncluded: false;\n /** Hard invariant: credentials are never embedded in Jira write payloads. */\n credentialsIncluded: false;\n}\n\n/**\n * FinOps budget + operational controls for test-intelligence LLM jobs (Issue #1371).\n *\n * The FinOps surface lets an operator bound an LLM job's input/output token\n * usage, wall-clock duration, retry count, image payload size, and replay-cache\n * miss rate per role (`test_generation`, `visual_primary`, `visual_fallback`),\n * and persist a deterministic per-job `budget-report.json` under the job's\n * `finops/` artifact directory. The artifact is local-only by default and\n * never carries secrets, raw prompts, or image bytes.\n *\n * Hard invariants:\n * - Cache hits report zero token usage AND zero LLM call attempts.\n * - Wall-clock budget breach is FAIL CLOSED (`retryable: false`).\n * - Token / wall-clock budget breach STOPS the job before downstream work.\n * - The artifact records SHA-256 hashes of identity inputs only — never\n * prompt text, response content, or token strings.\n */\n\n/** Schema version for the persisted FinOps budget report artifact (Issue #1371). */\nexport const FINOPS_BUDGET_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Subdirectory under a run dir where FinOps artifacts are persisted. */\nexport const FINOPS_ARTIFACT_DIRECTORY = \"finops\" as const;\n\n/** Canonical filename for the FinOps budget report artifact. */\nexport const FINOPS_BUDGET_REPORT_ARTIFACT_FILENAME =\n \"budget-report.json\" as const;\n\n/**\n * Per-role discriminant used inside the FinOps surface. Mirrors the gateway\n * roles but is exported as its own list so policy gates can iterate roles\n * without depending on the gateway surface.\n */\nexport const ALLOWED_FINOPS_ROLES = [\n \"test_generation\",\n \"visual_primary\",\n \"visual_fallback\",\n \"jira_api_requests\",\n \"jira_paste_ingest\",\n \"custom_context_ingest\",\n] as const;\n\n/** Discriminant of an allowed FinOps role. */\nexport type FinOpsRole = (typeof ALLOWED_FINOPS_ROLES)[number];\n\n/** Static source labels tracked inside the per-source FinOps breakdown. */\nexport const ALLOWED_AGENT_SOURCE_LABELS = [\n \"manager\",\n \"judge_primary\",\n \"judge_secondary\",\n \"visual_primary\",\n \"visual_fallback\",\n \"generator\",\n \"coverage_planner\",\n \"risk_ranker\",\n \"adversarial_critic\",\n \"gap_finder\",\n \"repair_planner\",\n \"ir_mutation_oracle\",\n] as const;\n\n/** Discriminant of an allowed per-source FinOps label. */\nexport type AgentSourceLabel =\n | (typeof ALLOWED_AGENT_SOURCE_LABELS)[number]\n | `hook:${string}`;\n\n/** Allowed budget breach reasons. Discriminated for policy-readable diagnostics. */\nexport const ALLOWED_FINOPS_BUDGET_BREACH_REASONS = [\n \"max_input_tokens\",\n \"max_output_tokens\",\n \"max_wall_clock_ms\",\n \"max_retries\",\n \"max_attempts\",\n \"max_image_bytes\",\n \"max_total_input_tokens\",\n \"max_total_output_tokens\",\n \"max_total_wall_clock_ms\",\n \"max_replay_cache_miss_rate\",\n \"max_fallback_attempts\",\n \"max_live_smoke_calls\",\n \"max_estimated_cost\",\n \"jira_api_quota_exceeded\",\n \"jira_paste_quota_exceeded\",\n \"custom_context_quota_exceeded\",\n] as const;\n\n/** Discriminant of a FinOps budget breach reason. */\nexport type FinOpsBudgetBreachReason =\n (typeof ALLOWED_FINOPS_BUDGET_BREACH_REASONS)[number];\n\n/** Allowed terminal outcomes for a FinOps-tracked job. */\nexport const ALLOWED_FINOPS_JOB_OUTCOMES = [\n \"completed\",\n \"completed_cache_hit\",\n \"budget_exceeded\",\n \"policy_blocked\",\n \"validation_blocked\",\n \"visual_sidecar_failed\",\n \"export_refused\",\n \"gateway_failed\",\n] as const;\n\n/** Discriminant of the terminal job outcome the FinOps report records. */\nexport type FinOpsJobOutcome = (typeof ALLOWED_FINOPS_JOB_OUTCOMES)[number];\n\n/**\n * Per-role budget envelope. Every limit is optional; `undefined` means the\n * limit is not enforced for that role. Counters compare with `>` (strict\n * exceedance) — a usage that exactly equals a limit is allowed.\n */\nexport interface FinOpsRoleBudget {\n /** Cap on the gateway's pre-flight `estimateInputTokens` (per-request). */\n maxInputTokensPerRequest?: number;\n /** Cap on `max_completion_tokens` forwarded to the gateway (per-request). */\n maxOutputTokensPerRequest?: number;\n /** Aggregate input-token cap across every request the role makes. */\n maxTotalInputTokens?: number;\n /** Aggregate output-token cap across every request the role makes. */\n maxTotalOutputTokens?: number;\n /** Per-request wall-clock cap. Maps to `LlmGenerationRequest.maxWallClockMs`. */\n maxWallClockMsPerRequest?: number;\n /** Aggregate wall-clock cap across every request the role makes. */\n maxTotalWallClockMs?: number;\n /** Per-request retry cap. Maps to `LlmGenerationRequest.maxRetries`. */\n maxRetriesPerRequest?: number;\n /**\n * Maximum number of gateway attempts the role may make in total\n * (success-or-failure). Useful when the live smoke surface should\n * fire only N times.\n */\n maxAttempts?: number;\n /** Cap on the decoded image bytes per request (visual roles only). */\n maxImageBytesPerRequest?: number;\n /**\n * Maximum number of fallback-deployment attempts the visual role may make.\n * Enforced against `visual_fallback` only; ignored for other roles.\n */\n maxFallbackAttempts?: number;\n /**\n * Maximum number of live-smoke calls the role may make. Enforced when\n * the operator wires a live-smoke counter into the recorder; otherwise\n * treated as not-configured.\n */\n maxLiveSmokeCalls?: number;\n /**\n * Aggregate byte-ingest cap per job for non-LLM source-ingestion roles\n * (`jira_paste_ingest`, `custom_context_ingest`). Ignored for LLM roles.\n */\n maxIngestBytesPerJob?: number;\n}\n\n/**\n * Aggregate budget envelope for a job. The envelope is rendered into the\n * FinOps report verbatim so an operator can read the limits applied without\n * cross-referencing source code.\n */\nexport interface FinOpsBudgetEnvelope {\n /** Stable identifier for the budget profile (operator-supplied). */\n budgetId: string;\n /** Free-form version label for the budget profile. */\n budgetVersion: string;\n /** Aggregate wall-clock cap across the entire job, all roles combined. */\n maxJobWallClockMs?: number;\n /**\n * Maximum permitted replay-cache miss rate over the job (`misses / total`).\n * `undefined` disables the check. Range `[0, 1]`.\n */\n maxReplayCacheMissRate?: number;\n /**\n * Optional per-job estimated cost cap (currency-agnostic — the recorder\n * accepts caller-supplied per-1000-token rates). `undefined` disables the\n * check.\n */\n maxEstimatedCost?: number;\n /** Per-role budget records. Missing roles are unconstrained. */\n roles: {\n test_generation?: FinOpsRoleBudget;\n visual_primary?: FinOpsRoleBudget;\n visual_fallback?: FinOpsRoleBudget;\n jira_api_requests?: FinOpsRoleBudget;\n jira_paste_ingest?: FinOpsRoleBudget;\n custom_context_ingest?: FinOpsRoleBudget;\n };\n /**\n * Per-source quota caps for non-LLM ingestion roles. Checked before any\n * ingestion begins; breach emits the source-specific breach reason and\n * fails fast without writing any artifact.\n */\n sourceQuotas?: {\n /** Maximum Jira REST API calls per job. Default: `MAX_JIRA_API_REQUESTS_PER_JOB`. */\n maxJiraApiRequestsPerJob?: number;\n /** Maximum raw paste bytes per job. Default: `MAX_JIRA_PASTE_BYTES_PER_JOB`. */\n maxJiraPasteBytesPerJob?: number;\n /** Maximum custom-context input bytes per job. Default: `MAX_CUSTOM_CONTEXT_BYTES_PER_JOB`. */\n maxCustomContextBytesPerJob?: number;\n };\n}\n\n/**\n * Per-attempt cost-rate input. Operators can supply a flat per-1000-token\n * rate and a per-attempt fixed cost; the recorder multiplies usage to\n * produce `estimatedCost`. Cost is currency-agnostic (the operator chooses\n * the unit, e.g. USD or \"internal credits\"), and the report stamps the\n * caller-supplied label so consumers know what the number means.\n */\nexport interface FinOpsCostRate {\n /** Cost per 1000 input tokens. */\n inputTokenCostPer1k?: number;\n /** Cost per 1000 output tokens. */\n outputTokenCostPer1k?: number;\n /** Fixed per-attempt cost (e.g. minimum-charge / API-call premium). */\n fixedCostPerAttempt?: number;\n}\n\n/** Per-role cost rate map. Roles with no rate produce `estimatedCost = 0`. */\nexport interface FinOpsCostRateMap {\n /** Operator-supplied label describing the unit (e.g. \"USD\"). */\n currencyLabel: string;\n rates: {\n test_generation?: FinOpsCostRate;\n visual_primary?: FinOpsCostRate;\n visual_fallback?: FinOpsCostRate;\n jira_api_requests?: FinOpsCostRate;\n jira_paste_ingest?: FinOpsCostRate;\n custom_context_ingest?: FinOpsCostRate;\n };\n}\n\n/**\n * Per-role usage record. Aggregated across every gateway attempt the role\n * made during the job. Cache hits do NOT increment any counter except\n * `cacheHits`.\n */\nexport interface FinOpsRoleUsage {\n role: FinOpsRole;\n /** Deployment label observed (e.g. `gpt-oss-120b-mock`). Empty string when no attempt was made. */\n deployment: string;\n /** Last model revision observed for this role. Omitted when unknown. */\n modelRevision?: string;\n /** Last routing tier label observed for this role. Omitted when unknown. */\n tierLabel?: ModelRoutingTierLabel;\n /** Total LLM call attempts (success + failure). Cache hits do NOT increment. */\n attempts: number;\n /** Successful attempts. */\n successes: number;\n /** Failure attempts (any error class). */\n failures: number;\n /** Sum of input tokens reported by the gateway across all successful attempts. */\n inputTokens: number;\n /** Sum of output tokens reported by the gateway across all successful attempts. */\n outputTokens: number;\n /** Sum of decoded image-input bytes per request (visual roles only; 0 elsewhere). */\n imageBytes: number;\n /** Number of replay-cache hits attributed to this role. */\n cacheHits: number;\n /** Number of replay-cache misses attributed to this role. */\n cacheMisses: number;\n /** Number of attempts that selected a fallback deployment. */\n fallbackAttempts: number;\n /** Number of attempts that hit a non-mock gateway (live-smoke counter). */\n liveSmokeCalls: number;\n /** Sum of wall-clock duration across attempts, in milliseconds. */\n durationMs: number;\n /** Last finish reason observed (success path) — `undefined` if no success. */\n lastFinishReason?: LlmFinishReason;\n /** Last error class observed (failure path) — `undefined` if no failure. */\n lastErrorClass?: LlmGatewayErrorClass | \"schema_invalid_response\";\n /** Estimated cost contribution from this role (currency-agnostic). */\n estimatedCost: number;\n /**\n * Total bytes ingested by non-LLM ingest roles (`jira_paste_ingest`,\n * `custom_context_ingest`). Always `0` for LLM and visual roles.\n */\n ingestBytes: number;\n}\n\n/**\n * Single budget breach record. Multiple breaches may be stamped on a\n * single report; the consumer can pick the first by `rule` order.\n */\nexport interface FinOpsBudgetBreach {\n rule: FinOpsBudgetBreachReason;\n /** Affected role, or `undefined` for job-level rules. */\n role?: FinOpsRole;\n /** Numeric observed value (encoded as number for comparators). */\n observed: number;\n /** Numeric threshold that was breached. */\n threshold: number;\n /** Sanitized human-readable message — never carries tokens or PII. */\n message: string;\n}\n\nexport interface ResolvedFinOpsWallClockBudget {\n readonly mode: \"elastic\" | \"constant_override\";\n readonly role: \"test_generation\";\n readonly resolvedMs: number;\n readonly formulaMs: number;\n readonly overrideMs?: number;\n readonly caseCount: number;\n readonly judgePanelSize: number;\n readonly adversarialRounds: number;\n readonly visualSidecarEnabled: boolean;\n readonly coefficients: FinOpsWallClockBudgetPolicy;\n readonly breakdown: {\n readonly baseMs: number;\n readonly caseMs: number;\n readonly additionalJudgeMs: number;\n readonly adversarialRoundMs: number;\n readonly visualSidecarMs: number;\n readonly unclampedMs: number;\n readonly hardCeilingMs: number;\n };\n}\n\nexport interface FinOpsResolvedBudgetReport {\n readonly testGenerationWallClock: ResolvedFinOpsWallClockBudget;\n}\n\n/**\n * FinOps budget report artifact. Persisted under\n * `<runDir>/finops/budget-report.json`. The artifact is byte-stable per job\n * (sorted role list, deterministic breach order). Cache-hit jobs report no\n * gateway usage; the `outcome` reflects this verbatim.\n *\n * Negative invariants stamped explicitly so absence cannot be inferred:\n * - `secretsIncluded: false`\n * - `rawPromptsIncluded: false`\n * - `rawScreenshotsIncluded: false`\n */\nexport interface FinOpsBudgetReport {\n schemaVersion: typeof FINOPS_BUDGET_REPORT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n jobId: string;\n generatedAt: string;\n /** Verbatim copy of the budget envelope applied to this job. */\n budget: FinOpsBudgetEnvelope;\n /** Audit trail for runtime-resolved budget inputs and coefficients. */\n resolvedBudget?: FinOpsResolvedBudgetReport;\n /** Caller-supplied currency label. `undefined` when no rate map was supplied. */\n currencyLabel?: string;\n /** Sorted by `role`. Always lists every role, even when usage is zero. */\n roles: FinOpsRoleUsage[];\n /** Deterministic per-agent-source attribution sealed in attestation. */\n bySource: Readonly<\n Record<\n AgentSourceLabel,\n {\n costMinorUnits: number;\n tokensIn: number;\n tokensOut: number;\n callCount: number;\n inFlightDedupHits: number;\n idempotentReplayHits: number;\n /**\n * Optional per-attempt identifiers surfaced when a source emits\n * multiple logically-distinct attempts that share the same\n * `AgentSourceLabel` (Issue #1936 diversity passes). Omitted for\n * legacy single-pass runs so byte-stable FinOps artifacts stay\n * unchanged unless the feature is enabled.\n */\n attemptIds?: readonly string[];\n /**\n * Optional circuit-breaker states recorded in dispatch order for this\n * source. Present when callers surface per-attempt breaker decisions\n * (Issue #2069) and omitted for sources that never supplied one.\n */\n circuitBreakerStates?: readonly (\"closed\" | \"open\" | \"half_open\")[];\n /**\n * Optional deployment label this source ran against (Issue\n * #1932). Surfaces the **judge** deployment for\n * `judge_primary` / `judge_secondary` when the operator wired\n * a cross-model bundle so FinOps attribution distinguishes\n * the judge family from the generator family. Omitted from\n * the canonical-JSON wire payload when the source recorded\n * no deployment, so the `bySource` hash stays stable for\n * legacy single-model runs.\n */\n deployment?: string;\n /** Optional model revision recorded for this source. */\n modelRevision?: string;\n /** Optional routing tier label recorded for this source. */\n tierLabel?: ModelRoutingTierLabel;\n /**\n * Optional constrained-decoding attribution for this source. Present\n * only when at least one attempt recorded schema-constrained decoding\n * metadata, so legacy runs stay byte-stable.\n */\n constrainedDecoding?: {\n adapterId: LlmConstrainedDecodingAdapterId;\n enforcement: LlmConstrainedDecodingEnforcement;\n activeCallCount: number;\n fallbackCallCount: number;\n fallbackReasons?: readonly string[];\n adapterVersion?: string;\n };\n /**\n * Issue #2177 — distinct attested hosting regions observed for this\n * source while contributing to the run.\n */\n regionAttestation?: {\n distinctRegions: readonly RegionAttestationHostingRegion[];\n attestedCallCount: number;\n warningCount: number;\n };\n }\n >\n >;\n /** Aggregate counters across the `bySource` map. */\n bySourceTotal: {\n costMinorUnits: number;\n callCount: number;\n };\n /** Timestamp used when sealing the `bySource` payload. */\n bySourceSealedAt: string;\n /**\n * Issue #2177 — audit summary of the run's region attestations. Present\n * whenever at least one LLM call or cache hit recorded region evidence.\n */\n regionAttestation?: {\n distinctRegions: readonly RegionAttestationHostingRegion[];\n attestedCallCount: number;\n warningCount: number;\n };\n /**\n * Figma REST payload audit trail (Issue #2172). Records the resolved cap\n * applied to this run alongside the actual payload bytes ingested so ops\n * can observe cap-vs-actual without re-running the job. Omitted when the\n * runner did not surface payload metrics (e.g. dry-run / cache-hit\n * short-circuit) so legacy fixtures stay byte-stable.\n */\n figmaPayload?: {\n /** Resolved cap applied during this run, in bytes. */\n resolvedCapBytes: number;\n /** Actual REST payload size that the runner ingested, in bytes. */\n actualBytes: number;\n /** Soft default cap (bytes) used when no override was supplied. */\n defaultCapBytes: number;\n /** Hard ceiling (bytes) above which any override is rejected. */\n ceilingBytes: number;\n /** True when the operator passed `--max-figma-payload-bytes`. */\n overrideApplied: boolean;\n };\n /** Aggregate counters across every role. */\n totals: {\n inputTokens: number;\n outputTokens: number;\n attempts: number;\n successes: number;\n failures: number;\n cacheHits: number;\n cacheMisses: number;\n fallbackAttempts: number;\n liveSmokeCalls: number;\n durationMs: number;\n imageBytes: number;\n estimatedCost: number;\n /** `cacheHits / (cacheHits + cacheMisses)` clamped to `[0, 1]`. NaN → 0. */\n replayCacheHitRate: number;\n /** `cacheMisses / (cacheHits + cacheMisses)` clamped to `[0, 1]`. NaN → 0. */\n replayCacheMissRate: number;\n /** Alias for replay-cache hit rate at the prompt layer. */\n promptCacheHitRate: number;\n /** Alias for replay-cache miss rate at the prompt layer. */\n promptCacheMissRate: number;\n };\n /** Sorted by `(rule, role)`. Empty when no budget was breached. */\n breaches: FinOpsBudgetBreach[];\n /** Terminal job outcome the report attests. */\n outcome: FinOpsJobOutcome;\n /** Hard invariant — secrets are never embedded in this artifact. */\n secretsIncluded: false;\n /** Hard invariant — raw prompt or response text is never embedded. */\n rawPromptsIncluded: false;\n /** Hard invariant — image bytes are never embedded. */\n rawScreenshotsIncluded: false;\n}\n\n/**\n * Per-job LLM Bill of Materials (CycloneDX 1.6 ML-BOM, Issue #1378).\n *\n * The LBOM is emitted alongside the existing evidence manifest so an\n * operator can inventory the model chain, the curated few-shot bundle,\n * and the active policy profile that produced a given set of test cases.\n * Unlike the package SBOM (CycloneDX 1.5, generated by\n * `scripts/generate-cyclonedx.mjs`), the LBOM is per-job and lives under\n * the run directory.\n *\n * Hard invariants are stamped as CycloneDX metadata properties: no secrets,\n * no raw prompts, and no decoded image bytes. Only hash digests participate.\n *\n * The artifact validates against the CycloneDX 1.6 schema family\n * (CycloneDX 1.6 + JSF + SPDX-encoded license identifiers) in CI. Runtime\n * persistence still uses the zero-runtime-dependency structural validator\n * in `src/test-intelligence/lbom-emitter.ts`.\n */\n\n/** CycloneDX spec version targeted by the per-job LBOM. */\nexport const LBOM_CYCLONEDX_SPEC_VERSION = \"1.6\" as const;\n\n/** Schema version for the persisted per-job LBOM artifact. */\nexport const LBOM_ARTIFACT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Subdirectory under a run dir where the per-job LBOM is persisted. */\nexport const LBOM_ARTIFACT_DIRECTORY = \"lbom\" as const;\n\n/** Canonical filename for the per-job LBOM artifact. */\nexport const LBOM_ARTIFACT_FILENAME = \"ai-bom.cdx.json\" as const;\n\n/**\n * Allowed roles for an LBOM machine-learning-model component. Mirrors the\n * gateway role surface so a single artifact can describe the entire model\n * chain that produced a job's test cases.\n */\nexport const ALLOWED_LBOM_MODEL_ROLES = [\n \"test_generation\",\n \"visual_primary\",\n \"visual_fallback\",\n] as const;\n\n/** Discriminant of an LBOM model role. */\nexport type LbomModelRole = (typeof ALLOWED_LBOM_MODEL_ROLES)[number];\n\n/** Discriminant of an LBOM data-component kind. */\nexport type LbomDataKind = \"few_shot_bundle\" | \"policy_profile\";\n\n/** Hash entry on a CycloneDX 1.6 component. */\nexport interface LbomHash {\n /** Hash algorithm. Test Intelligence emits `SHA-256`. */\n alg: \"SHA-256\";\n /** Lowercase hex digest. */\n content: string;\n}\n\n/** Property entry on a CycloneDX 1.6 component (or root metadata). */\nexport interface LbomProperty {\n name: string;\n value: string;\n}\n\n/** External reference entry on a CycloneDX 1.6 component. */\nexport interface LbomExternalReference {\n type:\n | \"documentation\"\n | \"vcs\"\n | \"evidence\"\n | \"model-card\"\n | \"configuration\"\n | \"license\";\n url: string;\n}\n\n/** License entry. Test Intelligence emits SPDX identifiers. */\nexport interface LbomLicenseEntry {\n license: { id: string };\n}\n\n/** CycloneDX 1.6 modelCard.modelParameters surface. */\nexport interface LbomModelParameters {\n task: string;\n architectureFamily?: string;\n modelArchitecture?: string;\n}\n\n/** CycloneDX 1.6 modelCard.considerations surface. */\nexport interface LbomModelConsiderations {\n users?: string[];\n useCases?: string[];\n technicalLimitations?: string[];\n performanceTradeoffs?: string[];\n ethicalConsiderations?: Array<{\n name: string;\n mitigationStrategy?: string;\n }>;\n fairnessAssessments?: string[];\n}\n\n/**\n * CycloneDX 1.6 modelCard.quantitativeAnalysis.performanceMetrics entry.\n * Values are encoded as strings per the CycloneDX 1.6 spec.\n */\nexport interface LbomPerformanceMetric {\n type: string;\n value: string;\n slice?: string;\n confidenceInterval?: { lowerBound: string; upperBound: string };\n}\n\n/** CycloneDX 1.6 modelCard surface. */\nexport interface LbomModelCard {\n modelParameters?: LbomModelParameters;\n quantitativeAnalysis?: { performanceMetrics: LbomPerformanceMetric[] };\n considerations?: LbomModelConsiderations;\n properties?: LbomProperty[];\n}\n\n/** CycloneDX 1.6 component entry — model variant. */\nexport interface LbomModelComponent {\n type: \"machine-learning-model\";\n \"bom-ref\": string;\n name: string;\n version: string;\n description: string;\n publisher?: string;\n group?: string;\n hashes?: LbomHash[];\n licenses?: LbomLicenseEntry[];\n externalReferences?: LbomExternalReference[];\n properties: LbomProperty[];\n modelCard: LbomModelCard;\n}\n\n/** CycloneDX 1.6 component entry — data variant (bundle / policy). */\nexport interface LbomDataComponent {\n type: \"data\";\n \"bom-ref\": string;\n name: string;\n version: string;\n description: string;\n hashes: LbomHash[];\n properties: LbomProperty[];\n}\n\n/** CycloneDX 1.6 dependency edge. */\nexport interface LbomDependency {\n ref: string;\n dependsOn: string[];\n}\n\n/** CycloneDX 1.6 metadata.tools entry. */\nexport interface LbomToolComponent {\n type: \"application\";\n name: string;\n version: string;\n publisher: string;\n description: string;\n}\n\n/** CycloneDX 1.6 metadata.component entry — the BOM subject. */\nexport interface LbomSubjectComponent {\n type: \"application\";\n \"bom-ref\": string;\n name: string;\n version: string;\n description: string;\n properties: LbomProperty[];\n}\n\n/** CycloneDX 1.6 metadata block emitted by Test Intelligence. */\nexport interface LbomMetadata {\n timestamp: string;\n tools: { components: LbomToolComponent[] };\n component: LbomSubjectComponent;\n properties: LbomProperty[];\n}\n\n/**\n * Per-job LLM Bill of Materials document (CycloneDX 1.6 ML-BOM, Issue #1378).\n *\n * The shape mirrors the CycloneDX 1.6 JSON spec for fields Test Intelligence\n * populates. Optional CycloneDX fields the product does not use are\n * intentionally omitted from the type to keep emission and validation aligned\n * with what callers can audit.\n */\nexport interface Wave1ValidationLbomDocument {\n bomFormat: \"CycloneDX\";\n specVersion: typeof LBOM_CYCLONEDX_SPEC_VERSION;\n /** CycloneDX-required document version. Test Intelligence emits `1`. */\n version: 1;\n /** RFC-4122 UUID URN, deterministic from job identity. */\n serialNumber: string;\n metadata: LbomMetadata;\n components: Array<LbomModelComponent | LbomDataComponent>;\n dependencies: LbomDependency[];\n}\n\n/** Validation issue surfaced by `validateLbomDocument`. */\nexport interface LbomValidationIssue {\n /** Dotted JSON path of the offending field. */\n path: string;\n /** Stable diagnostic code consumers can switch on. */\n code:\n | \"missing_required_field\"\n | \"invalid_value\"\n | \"invalid_hash\"\n | \"invalid_type\"\n | \"invalid_serial_number\"\n | \"invalid_timestamp\"\n | \"duplicate_bom_ref\"\n | \"unknown_dependency_ref\"\n | \"raw_prompt_leak\"\n | \"raw_screenshot_leak\"\n | \"secret_leak\";\n message: string;\n}\n\n/** Result of `validateLbomDocument`. */\nexport interface LbomValidationResult {\n valid: boolean;\n issues: LbomValidationIssue[];\n}\n\n/**\n * Audit-timeline summary of the per-job LBOM emit. Carries the on-disk\n * filename, byte length, the canonical SHA-256 (matches the manifest\n * attestation), and a count of components by kind so a verifier can spot\n * \"only one model row\" regression without re-parsing the artifact.\n */\nexport interface Wave1ValidationLbomSummary {\n schemaVersion: typeof LBOM_ARTIFACT_SCHEMA_VERSION;\n /** Relative filename inside the run directory (`lbom/ai-bom.cdx.json`). */\n filename: string;\n /** Byte length of the persisted canonical JSON. */\n bytes: number;\n /** SHA-256 of the persisted canonical JSON (hex, lowercase). */\n sha256: string;\n /** Component-kind counts. */\n componentCounts: {\n models: number;\n data: number;\n };\n /** Whether the visual sidecar fallback path was taken in the run. */\n visualFallbackUsed: boolean;\n}\n\n/**\n * Schema version for the `EvidenceVerifyResponse` envelope returned by\n * `GET /workspace/jobs/:jobId/evidence/verify` (Issue #1380). Bump when a\n * backwards-incompatible field shape change ships.\n */\nexport const EVIDENCE_VERIFY_RESPONSE_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Stable failure-code surface for evidence verification. Re-uses the\n * existing `Wave1ValidationAttestationVerificationFailureCode` literals where\n * applicable so a single auditor can route on a unified vocabulary.\n */\nexport type EvidenceVerifyFailureCode =\n | \"manifest_unparseable\"\n | \"manifest_metadata_invalid\"\n | \"manifest_digest_witness_invalid\"\n | \"artifact_missing\"\n | \"artifact_mutated\"\n | \"artifact_resized\"\n | \"unexpected_artifact\"\n | \"visual_sidecar_evidence_missing\"\n | \"envelope_unparseable\"\n | \"envelope_payload_type_mismatch\"\n | \"envelope_payload_decode_failed\"\n | \"statement_unparseable\"\n | \"statement_type_mismatch\"\n | \"statement_predicate_type_mismatch\"\n | \"statement_predicate_invalid\"\n | \"subject_missing_artifact\"\n | \"subject_digest_mismatch\"\n | \"subject_unattested_artifact\"\n | \"signing_mode_mismatch\"\n | \"signature_required\"\n | \"signature_unsigned_envelope_carries_signatures\"\n | \"signature_invalid_keyid\"\n | \"signature_invalid_encoding\"\n | \"signature_unverified\"\n | \"bundle_missing\"\n | \"bundle_envelope_mismatch\"\n | \"bundle_public_key_missing\"\n | \"manifest_sha256_mismatch\"\n | \"bySource_hash_mismatch\";\n\n/** Stable check-kind labels surfaced in the `EvidenceVerifyResponse.checks` array. */\nexport type EvidenceVerifyCheckKind =\n | \"artifact_sha256\"\n | \"manifest_metadata\"\n | \"manifest_digest_witness\"\n | \"visual_sidecar_evidence\"\n | \"attestation_envelope\"\n | \"attestation_signatures\";\n\n/**\n * One row in the `checks` array. Carries enough context for an auditor\n * to identify which artifact / check passed or failed and (when failed)\n * why. Sorted deterministically so the response body is byte-stable\n * across consecutive verifications of the same on-disk run.\n */\nexport interface EvidenceVerifyCheck {\n kind: EvidenceVerifyCheckKind;\n /** Safe manifest-relative artifact filename or stable check identifier. */\n reference: string;\n ok: boolean;\n /** Failure code when `ok === false`. Omitted when `ok === true`. */\n failureCode?: EvidenceVerifyFailureCode;\n /** Optional structured detail attached to attestation checks. */\n signingMode?: Wave1ValidationAttestationSigningMode;\n}\n\n/** One row in the `failures` array. Flat, sorted by reference + code. */\nexport interface EvidenceVerifyFailure {\n code: EvidenceVerifyFailureCode;\n /** Safe manifest-relative artifact filename or stable check identifier. */\n reference: string;\n /** Operator-readable diagnostic. Never includes absolute paths or secrets. */\n message: string;\n}\n\n/**\n * Response body returned by `GET /workspace/jobs/:jobId/evidence/verify`\n * with HTTP status 200. Status 200 means \"verification completed\",\n * regardless of pass/fail outcome — `ok` carries the verdict. The body\n * never contains absolute paths, bearer tokens, prompt bodies, raw\n * test-case payloads, env values, or signer secret material; only safe\n * manifest-relative filenames, SHA-256 digests, and identity stamps appear.\n */\nexport interface EvidenceVerifyResponse {\n schemaVersion: typeof EVIDENCE_VERIFY_RESPONSE_SCHEMA_VERSION;\n /** ISO-8601 timestamp the verification completed at. */\n verifiedAt: string;\n jobId: string;\n /** Overall verdict: true iff `failures.length === 0`. */\n ok: boolean;\n /** SHA-256 of the canonical manifest bytes (computed in memory). */\n manifestSha256: string;\n /** Mirrors `manifest.schemaVersion` when readable. */\n manifestSchemaVersion?: string;\n /** Mirrors `manifest.testIntelligenceContractVersion` when readable. */\n testIntelligenceContractVersion?: string;\n /** Model deployment names per role from the manifest. */\n modelDeployments?: {\n testGeneration: string;\n visualPrimary?: string;\n visualFallback?: string;\n };\n /** Visual sidecar metadata when the manifest carries it. */\n visualSidecar?: {\n selectedDeployment?: string;\n fallbackUsed: boolean;\n resultArtifactSha256?: string;\n captureIdentityCount?: number;\n };\n /** Attestation summary when an attestation envelope is on disk. */\n attestation?: {\n present: boolean;\n signingMode: Wave1ValidationAttestationSigningMode;\n signatureCount: number;\n signaturesVerified: boolean;\n };\n /** Per-artifact + per-check verification results. */\n checks: EvidenceVerifyCheck[];\n /** Flat list of every failed check, sorted by `reference`+`code`. */\n failures: EvidenceVerifyFailure[];\n}\n\n/* ============================================================\n * Delta + deduplication + traceability matrix (Issue #1373).\n *\n * Wave 3 introduces three additive, fail-closed surfaces:\n *\n * 1. Intent delta — pure compare of two `BusinessTestIntentIr`\n * artifacts producing an `IntentDeltaReport` covering screens,\n * fields, actions, validations, navigation, and the visual\n * addendum (visual fixture hash, `VisualScreenDescription`\n * hash, confidence/ambiguity drift).\n * 2. Dedupe report — the existing lexical fingerprint path\n * (`detectDuplicateTestCases`) extended with a pluggable\n * `EmbeddingProvider` (caller-supplied; default `null` =\n * lexical-only / air-gapped) and an OPTIONAL injected\n * cross-job/QC-folder probe. Both extensions are off by\n * default so the air-gapped flow is preserved.\n * 3. Traceability matrix — joins Figma node → IR element →\n * generated test case → QC mapping preview → transferred QC\n * id (when transfer-report present) → visual sidecar →\n * reconciliation/policy/validation outcomes.\n *\n * Every persisted artifact stamps the type-level hard invariants\n * `rawScreenshotsIncluded: false` and `secretsIncluded: false`\n * so a downstream consumer can verify they were produced under\n * the air-gapped/zero-secret-leak contract.\n * ============================================================ */\n\n/** Schema version for the persisted intent-delta artifact (Issue #1373). */\nexport const INTENT_DELTA_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Schema version for the persisted test-case delta report artifact (Issue #1373). */\nexport const TEST_CASE_DELTA_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted intent-delta artifact. */\nexport const INTENT_DELTA_REPORT_ARTIFACT_FILENAME =\n \"intent-delta-report.json\" as const;\n\n/** Canonical filename for the persisted test-case delta artifact. */\nexport const TEST_CASE_DELTA_REPORT_ARTIFACT_FILENAME =\n \"test-case-delta-report.json\" as const;\n\n/** Schema version for the persisted dedupe artifact (Issue #1373). */\nexport const DEDUPE_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted dedupe artifact. */\nexport const DEDUPE_REPORT_ARTIFACT_FILENAME = \"dedupe-report.json\" as const;\n\n/**\n * Schema version for the persisted parallel-pass case-merger artifact\n * (Issue #1937). The case-merger consumes two `GeneratedTestCaseList`s\n * emitted by parallel diversity passes (Issue #1936) and emits a single\n * deterministic merged list plus a per-case provenance/conflict log so\n * downstream auditors can reconstruct which pass contributed each case.\n */\nexport const CASE_MERGER_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted case-merger artifact. */\nexport const CASE_MERGER_REPORT_ARTIFACT_FILENAME =\n \"case-merger-report.json\" as const;\n\n/** Provenance of a merged test case relative to the two input passes. */\nexport const ALLOWED_CASE_MERGER_PROVENANCES = [\n \"runA\",\n \"runB\",\n \"both\",\n] as const;\nexport type CaseMergerProvenance =\n (typeof ALLOWED_CASE_MERGER_PROVENANCES)[number];\n\n/**\n * Reason a merged entry was selected over its conflicting counterpart.\n *\n * - `no_conflict` — the case existed in only one pass (no choice required).\n * - `prefer_unrepaired` — both passes produced the case; one pass had\n * non-empty `repairInstructions` for it and the other did not, so the\n * un-repaired side won (Issue #1937 conflict-resolution rule 1).\n * - `positive_bias_run_a` — both passes produced the case with comparable\n * repair-state, so pass A wins (Issue #1937 conflict-resolution rule 2).\n */\nexport const ALLOWED_CASE_MERGER_CONFLICT_RESOLUTIONS = [\n \"no_conflict\",\n \"prefer_unrepaired\",\n \"positive_bias_run_a\",\n] as const;\nexport type CaseMergerConflictResolution =\n (typeof ALLOWED_CASE_MERGER_CONFLICT_RESOLUTIONS)[number];\n\n/**\n * One row of the case-merger report. Each entry corresponds to a single\n * merged test case. `signature` is the dedup key\n * `(screenId, sorted(coveredFieldIds), sorted(coveredActionIds), technique)`\n * canonical-JSON-serialised so two entries with identical signatures are\n * guaranteed to have been merged (or, equivalently, the report has at most\n * one entry per signature).\n */\nexport interface CaseMergerReportEntry {\n readonly testCaseId: string;\n readonly provenance: CaseMergerProvenance;\n readonly signature: string;\n readonly technique: TestCaseTechnique29119;\n readonly screenId: string;\n readonly coveredFieldIds: readonly string[];\n readonly coveredActionIds: readonly string[];\n readonly conflictResolution: CaseMergerConflictResolution;\n /**\n * The id of the test case that lost the conflict (only populated when\n * `provenance === \"both\"`). The losing case's `qualitySignals` are merged\n * into the winning entry's coverage sets — see\n * `qualitySignalsCoverageMerged`.\n */\n readonly droppedTestCaseId?: string;\n /**\n * Whether the winning case absorbed coverage ids from the losing pass's\n * counterpart. When `true`, the merged test case carries a strict superset\n * of either side's `coveredFieldIds` / `coveredActionIds`.\n */\n readonly qualitySignalsCoverageMerged: boolean;\n}\n\n/** Persisted case-merger artifact (Issue #1937). */\nexport interface CaseMergerReport {\n readonly schemaVersion: typeof CASE_MERGER_REPORT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly jobId: string;\n readonly generatedAt: string;\n readonly totals: {\n readonly runACount: number;\n readonly runBCount: number;\n readonly mergedCount: number;\n readonly onlyInRunA: number;\n readonly onlyInRunB: number;\n readonly inBoth: number;\n readonly conflictsResolvedByRepair: number;\n readonly conflictsResolvedByPositiveBias: number;\n };\n readonly entries: readonly CaseMergerReportEntry[];\n}\n\n/** Schema version for the persisted traceability-matrix artifact (Issue #1373). */\nexport const TRACEABILITY_MATRIX_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the persisted traceability-matrix artifact. */\nexport const TRACEABILITY_MATRIX_ARTIFACT_FILENAME =\n \"traceability-matrix.json\" as const;\n\n/**\n * Allowed kinds of delta entries inside the intent-delta report.\n * Sorted, additive — additional kinds may be appended in future\n * minors.\n */\nexport const ALLOWED_INTENT_DELTA_KINDS = [\n \"screen\",\n \"field\",\n \"action\",\n \"validation\",\n \"navigation\",\n \"visual_screen\",\n] as const;\nexport type IntentDeltaKind = (typeof ALLOWED_INTENT_DELTA_KINDS)[number];\n\n/**\n * Allowed change types on a single delta entry.\n *\n * - `added` — present in current, absent in prior.\n * - `removed` — present in prior, absent in current.\n * - `changed` — present in both, but the canonical-hash differs.\n * - `confidence_dropped` — visual confidence (mean) fell more than\n * the configured drift threshold.\n * - `ambiguity_increased` — visual ambiguity / open-question count\n * grew between revisions.\n */\nexport const ALLOWED_INTENT_DELTA_CHANGE_TYPES = [\n \"added\",\n \"removed\",\n \"changed\",\n \"confidence_dropped\",\n \"ambiguity_increased\",\n] as const;\nexport type IntentDeltaChangeType =\n (typeof ALLOWED_INTENT_DELTA_CHANGE_TYPES)[number];\n\n/** Single delta entry inside `IntentDeltaReport.entries`. */\nexport interface IntentDeltaEntry {\n kind: IntentDeltaKind;\n changeType: IntentDeltaChangeType;\n /** Stable identifier inside the IR (e.g. `screenId`, `field.id`). */\n elementId: string;\n /** Owning screen id, when the entry is screen-scoped. */\n screenId?: string;\n /** SHA-256 hex of the prior canonical projection, when present. */\n priorHash?: string;\n /** SHA-256 hex of the current canonical projection, when present. */\n currentHash?: string;\n /** Optional sanitized human-readable detail (no PII, no tokens). */\n detail?: string;\n}\n\n/** Hard-invariant intent-delta report artifact (Issue #1373). */\nexport interface IntentDeltaReport {\n schemaVersion: typeof INTENT_DELTA_REPORT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n jobId: string;\n generatedAt: string;\n /** SHA-256 of the canonical prior IR (anchors the comparison). */\n priorIntentHash: string;\n /** SHA-256 of the canonical current IR (anchors the comparison). */\n currentIntentHash: string;\n /** Sorted-by-(kind,elementId,changeType) deterministic entries. */\n entries: IntentDeltaEntry[];\n /** Aggregate counts, computed deterministically from `entries`. */\n totals: {\n added: number;\n removed: number;\n changed: number;\n confidenceDropped: number;\n ambiguityIncreased: number;\n };\n /** Hard invariant: image bytes are NEVER embedded into this artifact. */\n rawScreenshotsIncluded: false;\n /** Hard invariant: tokens / credentials are NEVER embedded. */\n secretsIncluded: false;\n}\n\n/**\n * Per-test-case verdict produced by the test-case delta classifier.\n *\n * - `new` — case id present in current generation, absent from\n * prior generation.\n * - `unchanged` — case id present in both with identical\n * fingerprint AND no upstream IR delta touching its trace screens.\n * - `changed` — case id present in both, fingerprint differs OR\n * an IR delta touches one of the case's `figmaTraceRefs`.\n * - `obsolete` — case id present in prior generation but EVERY\n * trace screen is absent from the current IR. Reported only —\n * never destructively removed from QC (per Issue #1373 AC3).\n * - `requires_review` — visual confidence dropped below threshold\n * OR a reconciliation conflict surfaced.\n */\nexport const ALLOWED_TEST_CASE_DELTA_VERDICTS = [\n \"new\",\n \"unchanged\",\n \"changed\",\n \"obsolete\",\n \"requires_review\",\n] as const;\nexport type TestCaseDeltaVerdict =\n (typeof ALLOWED_TEST_CASE_DELTA_VERDICTS)[number];\n\n/**\n * Allowed reasons attached to a test-case delta verdict. Sorted,\n * additive. Multiple reasons may apply to the same verdict.\n */\nexport const ALLOWED_TEST_CASE_DELTA_REASONS = [\n \"absent_in_current\",\n \"absent_in_prior\",\n \"fingerprint_changed\",\n \"trace_screen_changed\",\n \"trace_screen_removed\",\n \"visual_ambiguity_increased\",\n \"visual_confidence_dropped\",\n \"reconciliation_conflict\",\n] as const;\nexport type TestCaseDeltaReason =\n (typeof ALLOWED_TEST_CASE_DELTA_REASONS)[number];\n\n/** Single per-case classification row. */\nexport interface TestCaseDeltaRow {\n testCaseId: string;\n verdict: TestCaseDeltaVerdict;\n /** Sorted, deduplicated reasons that fired. */\n reasons: TestCaseDeltaReason[];\n /** Sorted figma screen ids implicated by this row. */\n affectedScreenIds: string[];\n /** SHA-256 hex of the prior fingerprint when present. */\n priorFingerprintHash?: string;\n /** SHA-256 hex of the current fingerprint when present. */\n currentFingerprintHash?: string;\n}\n\n/** Aggregate test-case delta report (always paired with `IntentDeltaReport`). */\nexport interface TestCaseDeltaReport {\n schemaVersion: typeof TEST_CASE_DELTA_REPORT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n jobId: string;\n generatedAt: string;\n rows: TestCaseDeltaRow[];\n totals: {\n new: number;\n unchanged: number;\n changed: number;\n obsolete: number;\n requiresReview: number;\n };\n rawScreenshotsIncluded: false;\n secretsIncluded: false;\n}\n\n/**\n * Allowed similarity sources for a duplicate finding inside the\n * dedupe report.\n *\n * - `lexical` — Jaccard over the existing lexical fingerprint\n * (`buildTestCaseFingerprint`). Always available.\n * - `embedding` — cosine similarity over a caller-supplied\n * embedding vector. Only fires when an `EmbeddingProvider` is\n * injected.\n * - `external_lookup` — duplicate of an existing entity in an\n * external QC folder, surfaced via an injected probe. Only\n * fires when the optional probe is configured.\n */\nexport const ALLOWED_DEDUPE_SIMILARITY_SOURCES = [\n \"lexical\",\n \"embedding\",\n \"external_lookup\",\n] as const;\nexport type DedupeSimilaritySource =\n (typeof ALLOWED_DEDUPE_SIMILARITY_SOURCES)[number];\n\n/** Single internal duplicate finding (within the current job). */\nexport interface DedupeInternalFinding {\n source: Exclude<DedupeSimilaritySource, \"external_lookup\">;\n leftTestCaseId: string;\n rightTestCaseId: string;\n /** Similarity in [0, 1], rounded to 6 digits. */\n similarity: number;\n}\n\n/** Single external duplicate finding (against an external QC folder). */\nexport interface DedupeExternalFinding {\n source: \"external_lookup\";\n testCaseId: string;\n externalIdCandidate: string;\n /** Resolved folder path of the existing entity in the target system. */\n matchedFolderPath?: string;\n /**\n * Stable opaque identifier of the matched entity in the target\n * system. Treated as opaque — never logged or persisted alongside\n * any URL or token.\n */\n matchedEntityId?: string;\n}\n\n/**\n * Allowed informational outcomes of an external dedup probe.\n *\n * - `disabled` — caller did not configure an `externalProbe`.\n * - `unconfigured` — probe was supplied but reported its own\n * `unconfigured` verdict (e.g. air-gapped client). Fail-closed.\n * - `partial_failure` — at least one external lookup succeeded, but\n * one or more cases could not be checked. Fail-closed.\n * - `executed` — probe ran and returned per-case verdicts.\n */\nexport const ALLOWED_DEDUPE_EXTERNAL_PROBE_STATES = [\n \"disabled\",\n \"unconfigured\",\n \"partial_failure\",\n \"executed\",\n] as const;\nexport type DedupeExternalProbeState =\n (typeof ALLOWED_DEDUPE_EXTERNAL_PROBE_STATES)[number];\n\n/** Per-case verdict computed from the dedupe pipeline. */\nexport interface DedupeCaseVerdict {\n testCaseId: string;\n /**\n * `true` when the case has at least one internal duplicate\n * finding above the configured threshold OR an external\n * lookup match.\n */\n isDuplicate: boolean;\n /** Sorted-and-deduplicated list of similarity sources that fired. */\n matchedSources: DedupeSimilaritySource[];\n /** Highest similarity observed for this case across internal sources. */\n maxInternalSimilarity: number;\n}\n\n/** Aggregate dedupe report artifact (Issue #1373). */\nexport interface TestCaseDedupeReport {\n schemaVersion: typeof DEDUPE_REPORT_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n jobId: string;\n generatedAt: string;\n /** Threshold above which lexical similarity is reported (0..1). */\n lexicalThreshold: number;\n /** Threshold above which embedding similarity is reported (0..1). */\n embeddingThreshold?: number;\n /** Whether the embedding path participated in the run. */\n embeddingProvider: { configured: boolean; identifier?: string };\n externalProbe: {\n state: DedupeExternalProbeState;\n /** Number of test cases probed; zero on `disabled`/`unconfigured`. */\n cases: number;\n /** Sanitized informational note when the probe declined to run. */\n note?: string;\n };\n internalFindings: DedupeInternalFinding[];\n externalFindings: DedupeExternalFinding[];\n perCase: DedupeCaseVerdict[];\n totals: {\n duplicates: number;\n internalLexical: number;\n internalEmbedding: number;\n externalMatches: number;\n };\n rawScreenshotsIncluded: false;\n secretsIncluded: false;\n}\n\n/**\n * Single row inside the traceability matrix. Joins the lifecycle\n * of one generated test case across its Figma source, IR\n * elements, QC mapping, transfer outcome, visual sidecar\n * observations, and validation/policy outcomes.\n */\nexport interface TraceabilityMatrixRow {\n testCaseId: string;\n /** Title at the moment the matrix was built. */\n title: string;\n /** Sorted Figma screen ids that motivated the case. */\n figmaScreenIds: string[];\n /**\n * Sorted Figma node ids that motivated the case. Empty when no\n * trace ref carries a node id.\n */\n figmaNodeIds: string[];\n /** Sorted IR field ids covered by this case. */\n intentFieldIds: string[];\n /** Sorted IR action ids covered by this case. */\n intentActionIds: string[];\n /** Sorted IR validation ids covered by this case. */\n intentValidationIds: string[];\n /** Sorted IR navigation ids covered by this case. */\n intentNavigationIds: string[];\n /** Deterministic external-id candidate for the QC mapping. */\n externalIdCandidate?: string;\n /** Resolved target QC folder path under the export profile. */\n qcFolderPath?: string;\n /** Resolved QC entity id when the case was transferred. */\n qcEntityId?: string;\n /** Outcome of the transfer pipeline for this case, when known. */\n transferOutcome?: TransferEntityOutcome;\n /** Per-screen visual sidecar observations relevant to this case. */\n visualObservations: TraceabilityVisualObservation[];\n /** Per-step traceability rows derived from generated and QC design steps. */\n steps: TraceabilityStepRow[];\n /** Reconciliation decisions: one row per IR element with explicit provenance. */\n reconciliationDecisions: TraceabilityReconciliationDecision[];\n /** Per-case validation outcome — `error` if any error issue was raised. */\n validationOutcome: \"ok\" | \"warning\" | \"error\";\n /** Per-case policy decision (mirrors `TestCasePolicyDecisionRecord.decision`). */\n policyDecision?: TestCasePolicyDecision;\n /** Per-case sorted, deduplicated policy outcome codes that fired. */\n policyOutcomes: TestCasePolicyOutcome[];\n /** Review-state snapshot at the moment the matrix was built. */\n reviewState?: ReviewState;\n}\n\n/** Single ordered step row inside a traceability matrix row. */\nexport interface TraceabilityStepRow {\n stepIndex: number;\n action: string;\n expected?: string;\n /** Sorted Figma screen ids inherited from the test-case trace refs. */\n figmaScreenIds: string[];\n /** Sorted Figma node ids inherited from the test-case trace refs. */\n figmaNodeIds: string[];\n /** Matching QC design-step index when the mapping preview carries one. */\n qcDesignStepIndex?: number;\n /** Per-screen visual sidecar observations available for the step's case. */\n visualObservations: TraceabilityVisualObservation[];\n /** Per-case validation outcome at the time this step row was built. */\n validationOutcome: \"ok\" | \"warning\" | \"error\";\n /** Per-case policy decision at the time this step row was built. */\n policyDecision?: TestCasePolicyDecision;\n /** Per-case sorted, deduplicated policy outcomes at the time this step row was built. */\n policyOutcomes: TestCasePolicyOutcome[];\n}\n\n/** Single per-screen visual observation row inside the matrix. */\nexport interface TraceabilityVisualObservation {\n screenId: string;\n deployment: SidecarDeployment;\n /** Sorted, deduplicated outcome codes that fired on the screen. */\n outcomes: VisualSidecarValidationOutcome[];\n meanConfidence: number;\n}\n\n/** Single reconciliation decision row inside the matrix. */\nexport interface TraceabilityReconciliationDecision {\n screenId: string;\n elementId: string;\n /** IR provenance after reconciliation. */\n provenance: IntentProvenance;\n confidence: number;\n /** Sanitized ambiguity reason, when present. */\n ambiguity?: string;\n}\n\n/** Aggregate traceability-matrix artifact (Issue #1373). */\nexport interface TraceabilityMatrix {\n schemaVersion: typeof TRACEABILITY_MATRIX_SCHEMA_VERSION;\n contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n jobId: string;\n generatedAt: string;\n /** Identity of the export profile in play, when one is supplied. */\n exportProfile?: { id: string; version: string };\n /** Identity of the policy profile in play, when one is supplied. */\n policyProfile?: { id: string; version: string };\n rows: TraceabilityMatrixRow[];\n totals: {\n rows: number;\n transferred: number;\n failed: number;\n skippedDuplicate: number;\n refused: number;\n };\n rawScreenshotsIncluded: false;\n secretsIncluded: false;\n}\n\n/**\n * Jira capability probe result.\n */\nexport interface JiraCapabilityProbe {\n version: string;\n deploymentType: \"Cloud\" | \"Server\" | \"DataCenter\" | \"unknown\";\n adfSupported: boolean;\n}\n\n/**\n * Client configuration for the Jira REST gateway (Wave 4.C).\n */\nexport interface JiraGatewayConfig {\n baseUrl: string;\n auth:\n | { kind: \"bearer\"; token: string }\n | { kind: \"basic\"; email: string; apiToken: string }\n | { kind: \"oauth2_3lo\"; accessToken: string };\n userAgent: string;\n maxWallClockMs?: number;\n maxRetries?: number;\n maxResponseBytes?: number;\n /**\n * Exact hostnames or `*.example.com` suffix patterns allowed for Bearer\n * token/Data Center calls. Cloud Basic and OAuth gateway hosts are validated\n * by auth-mode-specific rules; Data Center endpoints must be allow-listed.\n */\n allowedHostPatterns?: readonly string[];\n}\n\n/**\n * Outbound fetch request shape for the Jira gateway.\n */\nexport interface JiraFetchRequest {\n query:\n | { kind: \"jql\"; jql: string; maxResults: number }\n | { kind: \"issueKeys\"; issueKeys: string[] };\n expand?: ReadonlyArray<\"renderedFields\" | \"names\" | \"schema\">;\n linkExpansionDepth?: 0 | 1 | 2;\n fieldSelection?: Partial<JiraFieldSelectionProfile>;\n maxWallClockMs?: number;\n maxRetries?: number;\n /** Enables deterministic on-disk gateway artifacts under `<runDir>/sources/<sourceId>/`. */\n runDir?: string;\n /** Source namespace used for replay/cache artifacts when `runDir` is set. */\n sourceId?: string;\n /** When true, load the persisted redacted Jira IR list and issue zero outbound fetches. */\n replayMode?: boolean;\n /** Deterministic capture timestamp for generated IR; defaults to Unix epoch. */\n capturedAt?: string;\n}\n\n/** Structured diagnostic emitted by the Jira gateway failure path. */\nexport interface JiraGatewayDiagnostic {\n code: string;\n message: string;\n retryable: boolean;\n status?: number;\n rateLimitReason?: string;\n}\n\n/**\n * Result returned by the Jira gateway.\n */\nexport interface JiraFetchResult {\n issues: JiraIssueIr[];\n capability: JiraCapabilityProbe;\n responseHash: string;\n retryable: boolean;\n attempts: number;\n diagnostic?: JiraGatewayDiagnostic;\n cacheHit?: boolean;\n}\n\n// ── Wave 4.I Production-Readiness Constants ──────────────────────────────────\n\n/**\n * Maximum Jira REST API calls allowed per production-readiness job.\n * Enforced before any outbound fetch; breach emits `jira_api_quota_exceeded`.\n *\n * Operator stance (CTO directive 2026-05-10): defaults raised to maximum\n * for stability + quality. Cost-aware profiles can override per customer.\n */\nexport const MAX_JIRA_API_REQUESTS_PER_JOB = 200 as const;\n\n/**\n * Maximum raw paste bytes allowed per production-readiness job.\n * Enforced before Jira paste ingest begins; breach emits `jira_paste_quota_exceeded`.\n *\n * Operator stance (CTO directive 2026-05-10): raised from 512 KiB to 8 MiB\n * to accommodate tier-1 banking Jira stories with embedded specifications.\n */\nexport const MAX_JIRA_PASTE_BYTES_PER_JOB = 8_388_608 as const;\n\n/**\n * Maximum custom-context input bytes allowed per production-readiness job.\n * Enforced before custom-context ingest begins; breach emits\n * `custom_context_quota_exceeded`.\n *\n * Operator stance (CTO directive 2026-05-10): raised from 256 KiB to 4 MiB\n * to accommodate multi-section custom-context Markdown files.\n */\nexport const MAX_CUSTOM_CONTEXT_BYTES_PER_JOB = 4_194_304 as const;\n\n/** Schema version for `Wave4ProductionReadinessEvalReport`. */\nexport const WAVE4_PRODUCTION_READINESS_EVAL_REPORT_SCHEMA_VERSION =\n \"1.0.0\" as const;\n\n/** On-disk filename for `Wave4ProductionReadinessEvalReport`. */\nexport const WAVE4_PRODUCTION_READINESS_EVAL_REPORT_ARTIFACT_FILENAME =\n \"wave4-production-readiness-eval-report.json\" as const;\n\n/** Source-mix identifier. Each distinct combination of source kinds is one mix. */\nexport type Wave4SourceMixId =\n | \"figma_only\"\n | \"jira_rest_only\"\n | \"jira_paste_only\"\n | \"figma_plus_jira_rest\"\n | \"figma_plus_jira_paste\"\n | \"jira_rest_plus_custom\"\n | \"figma_plus_jira_plus_custom\"\n | \"all_sources_with_conflict\"\n | \"custom_markdown_only\"\n | \"figma_plus_jira_plus_custom_markdown\"\n | \"custom_markdown_adversarial\";\n\n/** Pass/fail thresholds for the Wave 4 production-readiness eval gate. */\nexport interface Wave4ProductionReadinessEvalThresholds {\n /** Required provenance-field coverage across all sources (0–1). Default 1.0. */\n minSourceProvenance: number;\n /** Required source-attribution coverage on every test case (0–1). Default 1.0. */\n minTestCaseSourceAttribution: number;\n /** Minimum conflict-detection recall on the payment-with-conflict fixture (0–1). Default 0.95. */\n minConflictDetectionRecall: number;\n /** Maximum allowed outbound fetch calls in the air-gap fixture. Default 0. */\n maxAirgapFetchCalls: number;\n}\n\n/** Per-source-mix coverage entry emitted by the eval gate. */\nexport interface Wave4SourceMixCoverageEntry {\n mixId: Wave4SourceMixId;\n fixtureId: string;\n pass: boolean;\n /** Provenance coverage ratio (0–1). */\n sourceProvenanceCoverage: number;\n /** Source-attribution coverage ratio across test cases (0–1). */\n testCaseAttributionCoverage: number;\n conflictDetectionRecall?: number;\n airgapFetchCalls?: number;\n failureReasons: string[];\n}\n\n/**\n * Per-source provenance record in the evidence manifest.\n * One entry per source-IR artifact emitted under `<runDir>/sources/<sourceId>/`.\n */\nexport interface MultiSourceSourceProvenanceRecord {\n sourceId: string;\n kind: TestIntentSourceKind;\n contentHash: string;\n bytes: number;\n /** Author handle (reviewer-supplied for paste/custom sources). */\n authorHandle?: string;\n /** ISO-8601 capture timestamp. */\n capturedAt?: string;\n}\n\n/**\n * Evaluation report produced by the Wave 4 production-readiness gate.\n * Written to `<runDir>/wave4-production-readiness-eval-report.json`.\n */\nexport interface Wave4ProductionReadinessEvalReport {\n version: typeof WAVE4_PRODUCTION_READINESS_EVAL_REPORT_SCHEMA_VERSION;\n generatedAt: string;\n thresholds: Wave4ProductionReadinessEvalThresholds;\n passed: boolean;\n overallSourceProvenanceCoverage: number;\n overallTestCaseAttributionCoverage: number;\n sourceMixCoverage: Wave4SourceMixCoverageEntry[];\n markdownCustomContextCoverage: {\n totalMarkdownSources: number;\n sourcesWithProvenance: number;\n coverageRatio: number;\n };\n failureReasons: string[];\n rawScreenshotsIncluded: false;\n secretsIncluded: false;\n rawJiraResponsePersisted: false;\n rawPasteBytesPersisted: false;\n}\n\n// ---------------------------------------------------------------------------\n// Source-mix planner contracts (Issue #1441, Wave 4.K)\n// ---------------------------------------------------------------------------\n\n/** Schema version for persisted `source-mix-plan.json` artifacts. */\nexport const SOURCE_MIX_PLAN_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the deterministic source-mix plan artifact. */\nexport const SOURCE_MIX_PLAN_ARTIFACT_FILENAME =\n \"source-mix-plan.json\" as const;\n\n/**\n * All supported source-mix identifiers. Each value represents a distinct\n * combination of primary and supporting source kinds that the planner accepts.\n * The planner rejects any combination not listed here with\n * `unsupported_source_mix`.\n */\nexport const ALLOWED_TEST_INTENT_SOURCE_MIX_KINDS = [\n \"figma_only\",\n \"jira_rest_only\",\n \"jira_paste_only\",\n \"figma_jira_rest\",\n \"figma_jira_paste\",\n \"figma_jira_mixed\",\n \"jira_mixed\",\n] as const;\n\n/** Discriminated union of all supported source-mix kinds (Issue #1441). */\nexport type TestIntentSourceMixKind =\n (typeof ALLOWED_TEST_INTENT_SOURCE_MIX_KINDS)[number];\n\n/**\n * Prompt section tag identifying the role of a compiled source segment in the\n * LLM user prompt. The planner populates {@link SourceMixPlan.promptSections}\n * with the ordered list of sections that the prompt compiler must emit.\n *\n * - `figma_intent` — redacted Figma Business Test Intent IR.\n * - `jira_requirements` — one or more normalized Jira Issue IRs.\n * - `custom_context` — structured-attribute and/or plain-text custom context.\n * - `custom_context_markdown` — Markdown custom context (dedicated kind).\n * - `reconciliation_report` — cross-source conflict and field-provenance summary.\n */\nexport type SourceMixPlanPromptSection =\n | \"figma_intent\"\n | \"jira_requirements\"\n | \"custom_context\"\n | \"custom_context_markdown\"\n | \"reconciliation_report\";\n\n/**\n * Redacted source fingerprint material sealed into a source-mix plan.\n *\n * The planner records hashes only, never raw Jira responses, paste bytes, or\n * Markdown editor input. For Markdown context, the redacted Markdown and\n * plain-text derivative hashes are included so `sourceMixPlanHash` changes\n * when sanitized supporting evidence changes.\n */\nexport interface SourceMixPlanSourceDigest {\n /** Source ID from the multi-source envelope. */\n sourceId: string;\n /** Source kind from the multi-source envelope. */\n kind: TestIntentSourceKind;\n /** Canonical source content hash from the multi-source envelope. */\n contentHash: string;\n /** Canonical Jira issue key, when the source is Jira-backed. */\n canonicalIssueKey?: string;\n /** Redacted Markdown hash for Markdown supporting context. */\n redactedMarkdownHash?: string;\n /** Plain-text derivative hash for Markdown supporting context. */\n plainTextDerivativeHash?: string;\n}\n\n/**\n * Deterministic plan produced by the source-mix planner (Issue #1441).\n *\n * The plan captures which source combinations were selected for a job, what\n * visual-sidecar requirement applies, and in what order the prompt compiler\n * must emit role-tagged source sections. It also carries hash-only source\n * fingerprints so the `sourceMixPlanHash` changes when source content changes,\n * including redacted Markdown supporting context. The `sourceMixPlanHash`\n * participates in the replay-cache key so a different source mix always forces\n * a cache miss.\n *\n * Negative invariants (TYPE-LEVEL `false`):\n * - `figmaSourceRequired` is `false` on Jira-only and custom-enriched-Jira plans.\n * - `visualSidecarRequired` is `false` whenever `visualSidecarRequirement` is\n * `\"not_applicable\"`.\n * - `rawJiraResponsePersisted` is always `false` — only normalized IRs are stored.\n * - `rawPasteBytesPersisted` is always `false` — only normalized hashes are stored.\n */\nexport interface SourceMixPlan {\n /** Schema version stamp. */\n version: typeof SOURCE_MIX_PLAN_SCHEMA_VERSION;\n /** Discriminated mix kind derived from the source envelope. */\n kind: TestIntentSourceMixKind;\n /** Ordered source IDs classified as primary sources. */\n primarySourceIds: string[];\n /** Ordered source IDs classified as supporting sources. */\n supportingSourceIds: string[];\n /**\n * Whether the job requires a visual sidecar pass.\n * - `required` — at least one Figma source is present and visual captures are expected.\n * - `optional` — Figma is present but no capture set was supplied.\n * - `not_applicable` — Jira-only or custom-only; must be `false` at runtime.\n */\n visualSidecarRequirement: \"required\" | \"optional\" | \"not_applicable\";\n /**\n * Ordered list of prompt sections the compiler must emit for this plan.\n * The compiler must emit each listed section and MUST NOT emit unlisted sections.\n */\n promptSections: SourceMixPlanPromptSection[];\n /** Hash-only source fingerprints included in `sourceMixPlanHash` when emitted by the planner. */\n sourceDigests?: SourceMixPlanSourceDigest[];\n /**\n * SHA-256 of the canonical plan payload (computed before this field is set,\n * so the hash covers `kind`, `primarySourceIds`, `supportingSourceIds`,\n * `visualSidecarRequirement`, `promptSections`, and `sourceDigests`).\n */\n sourceMixPlanHash: string;\n /** Hard invariant: only normalized IRs are stored, never raw Jira API responses. */\n rawJiraResponsePersisted: false;\n /** Hard invariant: only redacted hashes are stored, never raw paste bytes. */\n rawPasteBytesPersisted: false;\n}\n\n/**\n * Refusal codes emitted by the source-mix planner when it rejects an envelope.\n * All refusals are fail-closed; no partial artifact is written.\n */\nexport const ALLOWED_SOURCE_MIX_PLANNER_REFUSAL_CODES = [\n \"primary_source_required\",\n \"unsupported_source_mix\",\n \"duplicate_source_id\",\n \"duplicate_jira_issue_key\",\n \"custom_markdown_hash_required\",\n \"custom_markdown_input_format_invalid\",\n \"source_mix_plan_hash_mismatch\",\n \"mode_gate_not_satisfied\",\n] as const;\n\n/** Refusal code alias for the source-mix planner. */\nexport type SourceMixPlannerRefusalCode =\n (typeof ALLOWED_SOURCE_MIX_PLANNER_REFUSAL_CODES)[number];\n\n/** A single validation issue surfaced by the source-mix planner. */\nexport interface SourceMixPlannerIssue {\n code: SourceMixPlannerRefusalCode;\n path?: string;\n detail?: string;\n}\n\n/** Result of source-mix planning (Issue #1441). */\nexport type SourceMixPlannerResult =\n | { ok: true; plan: SourceMixPlan }\n | { ok: false; issues: SourceMixPlannerIssue[] };\n\n/** Schema version for persisted `coverage-plan.json` artifacts. */\nexport const COVERAGE_PLAN_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the deterministic coverage-plan artifact. */\nexport const COVERAGE_PLAN_ARTIFACT_FILENAME = \"coverage-plan.json\" as const;\n\n/** Default mutation kill-rate target for deterministic coverage planning. */\nexport const DEFAULT_MUTATION_KILL_RATE_TARGET = 0.85 as const;\n\n/**\n * Technique identifiers selected by the deterministic coverage planner.\n *\n * These are plan-level test-design techniques, not the same enum as\n * `GeneratedTestCase.technique`.\n */\nexport const ALLOWED_COVERAGE_PLAN_TECHNIQUES = [\n \"initial_state\",\n \"equivalence_partitioning\",\n \"boundary_value\",\n \"decision_table\",\n \"state_transition\",\n \"pairwise\",\n \"error_guessing\",\n] as const;\n\n/** Discriminated union of deterministic coverage-planning techniques. */\nexport type CoveragePlanTechnique =\n (typeof ALLOWED_COVERAGE_PLAN_TECHNIQUES)[number];\n\n/**\n * Stable reason codes explaining why a coverage requirement exists.\n * These allow downstream generation and auditing to distinguish requirements\n * without parsing human-readable labels.\n */\nexport const ALLOWED_COVERAGE_REQUIREMENT_REASON_CODES = [\n \"acceptance_criterion\",\n \"screen_baseline\",\n \"element_partition\",\n \"rule_partition\",\n \"rule_boundary\",\n \"rule_decision\",\n \"action_transition\",\n \"field_lifecycle_transition\",\n \"field_lifecycle_error_transition\",\n \"calculation_rule\",\n \"screen_pairwise\",\n \"risk_regression\",\n \"open_question_probe\",\n \"source_reconciliation_probe\",\n \"supporting_context_probe\",\n] as const;\n\n/** Stable reason-code union for deterministic coverage requirements. */\nexport type CoverageRequirementReasonCode =\n (typeof ALLOWED_COVERAGE_REQUIREMENT_REASON_CODES)[number];\n\n/**\n * A single deterministic coverage requirement emitted by `coverage-planner.ts`.\n *\n * Each requirement is machine-readable and points at the model entities and\n * source refs that justified it. Human-readable wording is intentionally kept\n * out of the contract so equivalent inputs remain byte-stable.\n */\nexport interface CoverageRequirement {\n readonly requirementId: string;\n readonly technique: CoveragePlanTechnique;\n readonly reasonCode: CoverageRequirementReasonCode;\n readonly screenId?: string;\n readonly targetIds: readonly string[];\n readonly sourceRefs: readonly string[];\n readonly visualRefs: readonly string[];\n}\n\n/** Per-element risk classes reuse the generator's risk-category taxonomy. */\nexport type CoveragePlanElementRiskClass = TestCaseRiskCategory;\n\n/** Per-screen minimum quota for one planning technique. */\nexport interface CoveragePlanTechniqueQuota {\n readonly technique: TestCaseTechnique29119;\n readonly minCount: number;\n}\n\n/**\n * Screen-scoped quota bundle emitted by the coverage planner. Each screen lists\n * only the techniques that must appear at least `minCount` times.\n */\nexport interface CoveragePlanPerScreen {\n readonly screenId: string;\n readonly techniqueQuotas: readonly CoveragePlanTechniqueQuota[];\n}\n\n/** Per-element coverage target emitted by the coverage planner. */\nexport interface CoveragePlanPerElement {\n readonly screenId: string;\n readonly elementId: string;\n readonly mustHaveCase: boolean;\n readonly riskClass: CoveragePlanElementRiskClass;\n}\n\n/**\n * Deterministic pre-generation coverage plan derived from `TestDesignModel`\n * plus optional source-mix context.\n *\n * `mutationKillRateTarget` defaults to `0.85` when the caller does not supply\n * an override; callers may only provide values in the closed interval `[0, 1]`.\n */\nexport interface CoveragePlan {\n readonly schemaVersion: typeof COVERAGE_PLAN_SCHEMA_VERSION;\n readonly jobId: string;\n readonly perScreen: readonly CoveragePlanPerScreen[];\n readonly perElement: readonly CoveragePlanPerElement[];\n readonly minimumCases: readonly CoverageRequirement[];\n readonly recommendedCases: readonly CoverageRequirement[];\n readonly techniques: readonly CoveragePlanTechnique[];\n readonly mutationKillRateTarget: number;\n}\n\n/**\n * Issue #2068 — closed runtime list of `policy:technique-coverage-minimum`\n * resolution modes.\n *\n * - `tier-elastic` (default) — field-driven equivalence-partitioning quotas\n * scale with the screen's coverage-relevant field count using the formula\n * {@link TIER_ELASTIC_EP_TIERS}; use-case quotas are capped by field count\n * to avoid synthetic duplicate floors. Other techniques keep their planner\n * quotas unchanged.\n * - `fixed` — the planner quotas published in\n * `CoveragePlan.perScreen[].techniqueQuotas` are enforced verbatim.\n * Customers that contractually require a fixed minimum (e.g. a 12-EP\n * floor regardless of screen size) opt into this mode.\n */\nexport const TECHNIQUE_COVERAGE_MINIMUM_MODES = [\n \"tier-elastic\",\n \"fixed\",\n] as const;\n\n/** Discriminant of an allowed `policy:technique-coverage-minimum` mode. */\nexport type TechniqueCoverageMinimumMode =\n (typeof TECHNIQUE_COVERAGE_MINIMUM_MODES)[number];\n\n/**\n * Tier-elastic equivalence-partitioning quota tiers (Issue #2068).\n *\n * Every screen resolves to the LAST tier whose `minFieldCount <= fieldCount`\n * (sorted ascending). That tier then resolves to\n * `max(floor, ceil(multiplier * fieldCount))`. The tiers are intentionally a\n * frozen, deterministic constant so that\n * `policy-report.json` and `technique-quota-report.json` remain\n * byte-stable across runs.\n */\nexport interface TechniqueCoverageMinimumTier {\n readonly minFieldCount: number;\n readonly multiplier: number;\n readonly floor: number;\n readonly label: string;\n}\n\nexport const TIER_ELASTIC_EP_TIERS: ReadonlyArray<TechniqueCoverageMinimumTier> =\n Object.freeze([\n Object.freeze({\n minFieldCount: 0,\n multiplier: 2,\n floor: 4,\n label: \"fields<=4: max(4, 2*fields)\",\n }),\n Object.freeze({\n minFieldCount: 5,\n multiplier: 1.25,\n floor: 0,\n label: \"fields=5-8: ceil(1.25*fields)\",\n }),\n Object.freeze({\n minFieldCount: 9,\n multiplier: 0.9,\n floor: 0,\n label: \"fields=9-19: ceil(0.9*fields)\",\n }),\n Object.freeze({\n minFieldCount: 20,\n multiplier: 0.85,\n floor: 0,\n label: \"fields=20-29: ceil(0.85*fields)\",\n }),\n // Wave-5 W5-4 follow-up (2026-05-11): xr6Nf / Test-View-05 has 32\n // fields and the 0.85× tier required 28 EP cases while the generator\n // achieved 24 (75 %). Achieving 85 % EP coverage at this scale is\n // empirically hard; the floor relaxes for very large screens\n // (`fields >= 30`) so multi-section banking masks are not blocked at\n // the technique-coverage gate while still requiring meaningful\n // coverage (75 %).\n Object.freeze({\n minFieldCount: 30,\n multiplier: 0.75,\n floor: 0,\n label: \"fields>=30: ceil(0.75*fields)\",\n }),\n ]);\n\n/**\n * Issue #2068 / #2171 — policy-profile knob that drives the tier-elastic\n * resolution of `policy:technique-coverage-minimum`. Optional for backwards\n * compatibility; `undefined` is treated as `{ mode: \"tier-elastic\" }`. Issue\n * #2171 extends the tier-elastic branch with optional caller-supplied tiers\n * so the coefficients live in the policy profile instead of a hidden runtime\n * constant.\n */\nexport interface FixedTechniqueCoverageMinimumPolicy {\n readonly mode: \"fixed\";\n}\n\nexport interface TierElasticTechniqueCoverageMinimumPolicy {\n readonly mode: \"tier-elastic\";\n readonly tiers?: readonly TechniqueCoverageMinimumTier[];\n}\n\nexport type TechniqueCoverageMinimumPolicy =\n | FixedTechniqueCoverageMinimumPolicy\n | TierElasticTechniqueCoverageMinimumPolicy;\n\n/** Schema version for persisted `technique-quota-report.json` artifacts. */\nexport const TECHNIQUE_QUOTA_REPORT_SCHEMA_VERSION = \"1.1.0\" as const;\n\n/** Canonical filename for the per-run technique-quota-report artifact\n * (Issue #2068). */\nexport const TECHNIQUE_QUOTA_REPORT_ARTIFACT_FILENAME =\n \"technique-quota-report.json\" as const;\n\n/** Per-run resolution status for one (screen, technique) quota row. */\nexport const TECHNIQUE_QUOTA_REPORT_STATUSES = [\"pass\", \"deficit\"] as const;\n\nexport type TechniqueQuotaReportStatus =\n (typeof TECHNIQUE_QUOTA_REPORT_STATUSES)[number];\n\n/** One per-screen per-technique entry of `technique-quota-report.json`. */\nexport interface TechniqueQuotaReportEntry {\n readonly screenId: string;\n readonly technique: TestCaseTechnique29119;\n /** Coverage-relevant field count for the screen, derived from\n * `CoveragePlan.perElement`. */\n readonly fieldCount: number;\n /** Effective minimum the policy gate enforces this run. */\n readonly requiredCount: number;\n /** Cases anchored to the screen with this technique. */\n readonly actualCount: number;\n /** Stable, machine-readable formula label that produced\n * `requiredCount`. `tier-elastic:fields=9-19:ceil(0.9*fields)` etc. */\n readonly formula: string;\n /** Stable tier label consulted while resolving the quota. */\n readonly formulaTier: string;\n /** Multiplier applied by the consulted tier. `null` when the planner quota\n * was enforced verbatim and no elastic multiplier applied. */\n readonly formulaMultiplier: number | null;\n /** Mode that was active when the report was built. */\n readonly mode: TechniqueCoverageMinimumMode;\n readonly status: TechniqueQuotaReportStatus;\n}\n\n/**\n * Persistable per-run report capturing the\n * `policy:technique-coverage-minimum` resolution path (Issue #2068).\n *\n * The report is emitted on every run that has a `CoveragePlan`,\n * regardless of whether the gate passes — operators rely on it to audit\n * why a small-screen mask did NOT trigger a quota deficit even though\n * the planner published a fixed `12` minCount. Entries are sorted by\n * `(screenId, technique)`. */\nexport interface TechniqueQuotaReport {\n readonly schemaVersion: typeof TECHNIQUE_QUOTA_REPORT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly generatedAt: string;\n readonly jobId: string;\n readonly policyProfileId: string;\n readonly mode: TechniqueCoverageMinimumMode;\n readonly screenCount: number;\n readonly entryCount: number;\n readonly passCount: number;\n readonly deficitCount: number;\n readonly entries: readonly TechniqueQuotaReportEntry[];\n}\n\n/** Schema version for persisted `risk-ranking.json` artifacts (Issue #1935). */\nexport const RISK_RANKING_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the deterministic risk-ranking artifact. */\nexport const RISK_RANKING_ARTIFACT_FILENAME = \"risk-ranking.json\" as const;\n\n/**\n * Stable rationale tokens for a `RiskRankingElement` (Issue #1935).\n *\n * The deterministic baseline emits one of these tokens; LLM augmentation may\n * only re-order or raise scores within the same closed taxonomy. Tokens are\n * machine-readable so downstream consumers (generator prompt, judges) can\n * reason about rank reasons without parsing prose.\n */\nexport const ALLOWED_RISK_RANKING_RATIONALES = [\n \"policy_strict\",\n \"regulated_data\",\n \"financial_transaction\",\n \"high_risk_signal\",\n \"must_have_case\",\n \"medium_risk_signal\",\n \"baseline\",\n] as const;\n\n/** Discriminated union of allowed rationale tokens. */\nexport type RiskRankingRationale =\n (typeof ALLOWED_RISK_RANKING_RATIONALES)[number];\n\n/**\n * One ranked IR element. Each entry refers back to the same\n * `(screenId, elementId)` pair surfaced by `CoveragePlanPerElement` so the\n * ranking can be cross-referenced against the deterministic coverage plan.\n *\n * `riskScore` lies in the closed interval `[0, 1]`. Higher scores mean the\n * element should attract more cases in the generator output.\n */\nexport interface RiskRankingElement {\n readonly screenId: string;\n readonly elementId: string;\n readonly riskScore: number;\n readonly rationale: RiskRankingRationale;\n}\n\n/**\n * Deterministic risk ranking emitted by `risk-ranker.ts` (Issue #1935).\n *\n * The ranking augments `CoveragePlan` with a sorted priority list so the\n * generator prompt can require explicit coverage of the top-K elements. The\n * artifact is persisted as `risk-ranking.json` for downstream auditing.\n *\n * `topKElementIds` is the prefix of `rankedElements` whose `(screenId,\n * elementId)` pairs the generator MUST cover with at least one case each.\n */\nexport interface RiskRanking {\n readonly schemaVersion: typeof RISK_RANKING_SCHEMA_VERSION;\n readonly jobId: string;\n readonly rankedElements: readonly RiskRankingElement[];\n readonly topKElementIds: readonly string[];\n}\n\n/**\n * Deterministic mutation-coverage-strength report emitted by the\n * IR mutation oracle companion (Issue #1783).\n *\n * The report is intentionally minimal and machine-readable so the repair\n * planner can consume surviving mutations without reparsing prose. Arrays are\n * sorted deterministically by the runtime module.\n */\nexport interface IrMutationCoverageStrengthReport {\n readonly schemaVersion: \"1.0.0\";\n readonly jobId: string;\n readonly mutationCount: number;\n readonly killedMutations: number;\n readonly mutationKillRate: number;\n readonly perMutation: readonly {\n readonly mutationId: string;\n readonly mutationKind:\n | \"flip_required\"\n | \"shrink_boundary\"\n | \"drop_state_transition\"\n | \"swap_equivalence_class\"\n | \"invert_decision_rule\";\n readonly affectedSourceRefs: readonly string[];\n readonly killedByTestCaseIds: readonly string[];\n }[];\n readonly survivingMutationsForRepair: readonly string[];\n}\n\n// ---------------------------------------------------------------------------\n// Issue #1801 — release:quality-gates hard CI gates.\n// ---------------------------------------------------------------------------\n\n/**\n * Filename of the canonical-JSON release-quality-gates report emitted by\n * the release pipeline at `<runDir>/release-quality-gates.json`.\n */\nexport const RELEASE_QUALITY_GATES_REPORT_ARTIFACT_FILENAME =\n \"release-quality-gates.json\" as const;\n\n/** Schema version for the release-quality-gates report. */\nexport const RELEASE_QUALITY_GATES_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Hard release thresholds for Issue #1801 and Issue #1802. Each gate either\n * fails the release on breach or attributes the breach to a specific fixture,\n * role, or query source for diff-artifact review.\n */\nexport const RELEASE_QUALITY_GATES_THRESHOLDS: {\n readonly minMutationKillRate: 0.85;\n readonly minPromptCacheHitRate: 0.7;\n readonly maxCacheBreakRate: 0.05;\n readonly perSourceCostPlausibility: { readonly allowedFailures: 0 };\n readonly MEMDIR_MAX_AGE_MS: 7776000000;\n readonly contextBudget: {\n readonly defaultMaxBloatRatio: 1.2;\n readonly minSampleCount: 5;\n };\n} = {\n /** `mutationKillRate >= 0.85` against curated mutation fixtures. */\n minMutationKillRate: 0.85,\n /** `promptCacheHitRate >= 0.7` across repair iterations 2..N. */\n minPromptCacheHitRate: 0.7,\n /** `cacheBreakRate <= 5%` over the release sample. */\n maxCacheBreakRate: 0.05,\n /**\n * Gate 5 (Issue #1802): `allowedFailures: 0` means a single hash mismatch\n * or unsealed sample fails the entire gate.\n */\n perSourceCostPlausibility: { allowedFailures: 0 },\n /**\n * Gate 6 (Issue #1802): Maximum age for banking-profile lessons.\n * 90 days in milliseconds = 7_776_000_000.\n */\n MEMDIR_MAX_AGE_MS: 7776000000,\n /**\n * Gate 9 (Issue #1802): Context budget regression thresholds.\n * `defaultMaxBloatRatio` of 1.20 allows up to 20% token bloat before\n * failing — unless the quality delta score is >= 0.05 (material win).\n */\n contextBudget: { defaultMaxBloatRatio: 1.2, minSampleCount: 5 },\n} as const;\n\n/**\n * Closed list of allowed statuses for the library-coverage-status-completeness\n * release gate (Issue #1802, Gate 7). Distinct from\n * `ALLOWED_LIBRARY_PRIMITIVE_STATUSES` which tracks per-release implementation\n * snapshots; this constant tracks release-report coverage decisions.\n */\nexport const ALLOWED_LIBRARY_COVERAGE_RELEASE_STATUSES = [\n \"COVERED\",\n \"PARITY-PATH\",\n \"NICHT-UEBERNOMMEN\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_LIBRARY_COVERAGE_RELEASE_STATUSES}. */\nexport type LibraryCoverageReleaseStatus =\n (typeof ALLOWED_LIBRARY_COVERAGE_RELEASE_STATUSES)[number];\n\n/** Identifiers for all nine hard gates wired into release:quality-gates. */\nexport const ALLOWED_RELEASE_QUALITY_GATE_IDS = [\n \"mutation_kill_rate\",\n \"prompt_cache_hit_rate\",\n \"tamper_detection_round_trip\",\n \"cache_break_rate\",\n \"per_source_cost_plausibility\",\n \"memdir_manifest_consistency\",\n \"library_coverage_status_completeness\",\n \"architecture_fit_self_test\",\n \"context_budget_regression\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_RELEASE_QUALITY_GATE_IDS}. */\nexport type ReleaseQualityGateId =\n (typeof ALLOWED_RELEASE_QUALITY_GATE_IDS)[number];\n\n/**\n * Per-fixture mutation-kill input. Mirrors the relevant subset of\n * {@link IrMutationCoverageStrengthReport} but pinned to the curated\n * release fixture set so the gate is reproducible offline.\n */\nexport interface ReleaseQualityGateMutationFixture {\n readonly fixtureId: string;\n readonly mutationCount: number;\n readonly killedMutations: number;\n readonly mutationKillRate: number;\n readonly survivingMutationsForRepair: readonly string[];\n}\n\n/**\n * Per-role prompt-cache statistics across repair iterations 2..N\n * (iteration 1 is excluded — the first attempt cannot benefit from\n * cache reads of its own prompt prefix).\n */\nexport interface ReleaseQualityGatePromptCacheRole {\n readonly roleId: string;\n readonly iterationsCounted: number;\n readonly cacheHits: number;\n readonly cacheMisses: number;\n readonly promptCacheHitRate: number;\n}\n\n/**\n * Tamper-detection round-trip outcome per release job. The harness\n * Merkle chain + `headOfChainHash` + ML-BOM hash are verified offline\n * against the evidence manifest. A single failure across `samples`\n * fails the gate.\n */\nexport interface ReleaseQualityGateTamperSample {\n readonly sampleId: string;\n readonly merkleChainVerified: boolean;\n readonly headOfChainHashVerified: boolean;\n readonly mlBomHashVerified: boolean;\n}\n\n/**\n * Per-source cache-break observation. Used both to compute the global\n * `cacheBreakRate` and to attribute a spike to the offending source for\n * diff-artifact review.\n */\nexport interface ReleaseQualityGateCacheBreakSample {\n readonly querySource: string;\n readonly responseCount: number;\n readonly breakCount: number;\n readonly diffArtifactBasenames: readonly string[];\n}\n\n/**\n * Per-sample input for Gate 5 — per-source cost plausibility (Issue #1802).\n * Both hash fields must be lowercase hex64; `sealed` must be true for the\n * gate to pass.\n */\nexport interface ReleaseQualityGatePerSourceCostSample {\n readonly sampleId: string;\n readonly attestedBySourceHash: string;\n readonly observedBySourceHash: string;\n readonly sealed: boolean;\n}\n\n/**\n * Per-lesson input for Gate 6 — memdir manifest consistency (Issue #1802).\n * Banking-profile lessons are age-checked against `MEMDIR_MAX_AGE_MS`.\n * Non-banking lessons are included in the report for visibility but do not\n * cause the gate to fail.\n */\nexport interface ReleaseQualityGateMemdirLesson {\n readonly lessonId: string;\n readonly profile: \"banking\" | \"default\" | \"cross-tenant\";\n readonly mtimeMs: number;\n readonly lastRefreshAtMs?: number;\n readonly nowMs: number;\n}\n\n/**\n * Per-primitive input for Gate 7 — library coverage status completeness\n * (Issue #1802). Every primitive must have a non-empty justification and a\n * valid `LibraryCoverageReleaseStatus`. A `COVERED` entry with\n * `moduleImplemented === false` fails the gate.\n */\nexport interface ReleaseQualityGateLibraryCoveragePrimitive {\n readonly primitiveId: string;\n readonly status: LibraryCoverageReleaseStatus;\n readonly justification: string;\n readonly referencedModulePath?: string;\n readonly moduleImplemented: boolean;\n}\n\n/**\n * Per-violation record produced by `analyzeAgentBoundaries` for Gate 8 —\n * architecture fit self-test (Issue #1802).\n */\nexport interface ReleaseQualityGateArchitectureViolation {\n readonly file: string;\n readonly type: string;\n readonly line: number;\n}\n\n/**\n * Complete input envelope consumed by `evaluateReleaseQualityGates` and\n * by the release-quality-gates CLI runner. The first four sections (mutation,\n * promptCache, tamper, cacheBreak) were introduced in Issue #1801 and remain\n * required. The five new sections introduced in Issue #1802 are also required;\n * they are validated structurally but carry sensible defaults in the baseline\n * fixture so existing callers can migrate incrementally.\n */\nexport interface ReleaseQualityGatesInput {\n readonly schemaVersion: typeof RELEASE_QUALITY_GATES_REPORT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly releaseId: string;\n readonly mutation: {\n readonly fixtures: readonly ReleaseQualityGateMutationFixture[];\n };\n readonly promptCache: {\n readonly roles: readonly ReleaseQualityGatePromptCacheRole[];\n };\n readonly tamper: {\n readonly samples: readonly ReleaseQualityGateTamperSample[];\n };\n readonly cacheBreak: {\n readonly samples: readonly ReleaseQualityGateCacheBreakSample[];\n };\n /** Gate 5 — per-source cost plausibility (Issue #1802). */\n readonly perSourceCostPlausibility: {\n readonly samples: readonly ReleaseQualityGatePerSourceCostSample[];\n };\n /** Gate 6 — memdir manifest consistency (Issue #1802). */\n readonly memdirManifestConsistency: {\n readonly pathValidator: {\n readonly coveredCases: number;\n readonly totalCases: number;\n };\n readonly lessons: readonly ReleaseQualityGateMemdirLesson[];\n };\n /** Gate 7 — library coverage status completeness (Issue #1802). */\n readonly libraryCoverageStatusCompleteness: {\n readonly primitives: readonly ReleaseQualityGateLibraryCoveragePrimitive[];\n };\n /**\n * Gate 8 — architecture fit self-test (Issue #1802).\n * The runner populates this automatically from `analyzeAgentBoundaries`;\n * the pure evaluator accepts it as-is so tests can inject values directly.\n */\n readonly architectureFitSelfTest: {\n readonly scannedFileCount: number;\n readonly violations: readonly ReleaseQualityGateArchitectureViolation[];\n };\n /** Gate 9 — context budget regression (Issue #1802). */\n readonly contextBudgetRegression: {\n readonly baseline: {\n readonly meanInputTokens: number;\n readonly sampleCount: number;\n };\n readonly harness: {\n readonly meanInputTokens: number;\n readonly sampleCount: number;\n };\n readonly qualityDeltaScore: number;\n readonly maxBloatRatio?: number;\n };\n}\n\n/**\n * Per-gate verdict. `attribution[]` carries fixture/role/source\n * identifiers when the threshold was breached so a reviewer can jump to\n * the offending evidence without rerunning the pipeline.\n */\nexport interface ReleaseQualityGateVerdict {\n readonly gateId: ReleaseQualityGateId;\n readonly observed: number;\n readonly threshold: number;\n readonly comparator: \"gte\" | \"lte\" | \"eq\";\n readonly passed: boolean;\n readonly attribution: readonly string[];\n}\n\n/**\n * Canonical-JSON report emitted by the release-quality-gates runner.\n * The release pipeline fails when any verdict has `passed === false`.\n */\nexport interface ReleaseQualityGatesReport {\n readonly schemaVersion: typeof RELEASE_QUALITY_GATES_REPORT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly releaseId: string;\n readonly mutationKillRate: number;\n readonly promptCacheHitRate: number;\n readonly tamperDetectionPassed: boolean;\n readonly cacheBreakRate: number;\n readonly verdicts: readonly ReleaseQualityGateVerdict[];\n readonly passed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Audit dossier bundle surface (Issue #2175).\n// ---------------------------------------------------------------------------\n\n/** Canonical basename shared by audit-dossier bundle files. */\nexport const AUDIT_DOSSIER_ARTIFACT_BASENAME = \"audit-dossier\" as const;\n\n/** Schema version for the canonical machine-readable dossier manifest. */\nexport const AUDIT_DOSSIER_MANIFEST_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Schema version for the detached audit-dossier signature envelope. */\nexport const AUDIT_DOSSIER_SIGNATURE_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Closed list of source-artifact roles tracked by the dossier manifest. */\nexport const ALLOWED_AUDIT_DOSSIER_ARTIFACT_KINDS = [\n \"model_card\",\n \"provenance\",\n \"compliance_coverage\",\n \"compliance_annotations\",\n \"judge_calibration\",\n \"locale_calibration\",\n \"inter_rater_agreement\",\n \"drift_baseline\",\n \"incident_log\",\n \"subprocessor_register\",\n \"region_attestations\",\n \"finops_budget\",\n \"faithfulness_tier\",\n \"self_consistency\",\n \"evidence_seal\",\n \"policy_report\",\n \"human_review_log\",\n \"formal_verification_report\",\n \"tenant_bundle_resolved\",\n] as const;\n\nexport type AuditDossierManifestArtifactKind =\n (typeof ALLOWED_AUDIT_DOSSIER_ARTIFACT_KINDS)[number];\n\n/**\n * One attested input artifact consumed while assembling the dossier\n * bundle. The manifest carries only filenames, sizes, and digests —\n * never raw prompts, screenshots, secrets, or PII-bearing payloads.\n */\nexport interface AuditDossierManifestArtifactRef {\n readonly kind: AuditDossierManifestArtifactKind;\n readonly filename: string;\n readonly sha256: string;\n readonly bytes: number;\n}\n\n/** One provenance-leaf witness carried so the bundle can self-verify. */\nexport interface AuditDossierProvenanceLeafHash {\n readonly reference: string;\n readonly hash: string;\n}\n\n/** One regulator/article row in the human-readable evidence table. */\nexport interface AuditDossierRegulationCoverageEntry {\n readonly regulation: string;\n readonly requirement: string;\n readonly artifactKinds: readonly AuditDossierManifestArtifactKind[];\n readonly notes: readonly string[];\n}\n\n/**\n * Deterministic machine-readable dossier manifest. It is the canonical\n * signed payload; the PDF is a human-readable rendering of this surface.\n */\nexport interface AuditDossierManifest {\n readonly schemaVersion: typeof AUDIT_DOSSIER_MANIFEST_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly generatedAt: string;\n readonly runId: string;\n readonly bundle: {\n readonly jsonFilename: string;\n readonly signatureFilename: string;\n readonly pdfFilename: string;\n readonly merkleProofFilename: string;\n readonly pdfSha256: string;\n };\n readonly signing: {\n readonly algorithm: \"ed25519\";\n readonly keyFingerprintSha256: string;\n readonly publicKeyPem: string;\n readonly manifestSha256: string;\n };\n readonly provenance: {\n readonly algorithm: string;\n readonly merkleRoot: string;\n readonly leafCount: number;\n readonly leafHashes: readonly AuditDossierProvenanceLeafHash[];\n readonly merkleProofSha256: string;\n };\n readonly sourceArtifacts: readonly AuditDossierManifestArtifactRef[];\n readonly regionAttestations?: readonly {\n readonly filename: string;\n readonly distinctRegions: readonly RegionAttestationHostingRegion[];\n readonly attestationCount: number;\n }[];\n readonly formalVerification?: {\n readonly filename: string;\n readonly verdict: \"pass\" | \"fail\";\n readonly specCount: number;\n readonly formulaCount: number;\n readonly passCount: number;\n readonly failCount: number;\n readonly specs: readonly {\n readonly specPath: string;\n readonly module: string;\n readonly verdict: \"pass\" | \"fail\";\n readonly reachableStateCount: number;\n readonly formulaCount: number;\n readonly passCount: number;\n readonly failCount: number;\n }[];\n };\n /**\n * Self-improving judge-calibration refit history (Issue #2182).\n *\n * Optional and additive. Populated only when the audit-dossier\n * generator finds at least one production curve OR proposal in\n * `fixtures/test-intelligence/calibration-curves/`. Legacy runs that\n * do not consume the calibration-curves fixtures keep the dossier\n * shape stable.\n */\n readonly selfImprovingCalibrationRefitHistory?: {\n readonly productionCurveCount: number;\n readonly proposalCount: number;\n readonly ratifiedCount: number;\n readonly rolledBackCount: number;\n readonly rows: readonly {\n readonly locale: string;\n readonly riskClass: string;\n readonly proposalId: string;\n readonly status: \"ratified\" | \"open\" | \"rolled_back\";\n readonly proposedAt: string;\n readonly ratifiedAt?: string;\n readonly heldOutEce: number;\n readonly heldOutKappa: number;\n }[];\n };\n /**\n * Customer-specific configuration (Issue #2184). Populated only when\n * the audit-dossier generator finds `tenant-bundle-resolved.json`\n * under the run directory. Legacy runs that do not load a tenant\n * bundle keep the dossier shape stable.\n */\n readonly customerBundle?: {\n readonly filename: string;\n readonly tenantId: string;\n readonly bundleVersion: string;\n readonly inheritsFromPolicyProfile: string;\n readonly contentHash: string;\n readonly riskClassOverrideCount: number;\n readonly complianceHouseStandardCount: number;\n readonly designSystemTokenCount: number;\n readonly terminologyGlossaryCount: number;\n readonly hasNamingConvention: boolean;\n readonly hasCustomerEvalRubricRef: boolean;\n readonly appliedOverrides: readonly string[];\n };\n /**\n * Test-execution evidence loop summary (Issue #2186, W8-4). Optional\n * and additive: populated only when the dossier generator finds at\n * least one persisted execution-evidence record under the per-tenant\n * calibration corpus directory passed via\n * `executionEvidenceCorpusDir`. Tenants that have not yet ingested\n * TMS execution evidence keep the dossier shape stable.\n */\n readonly executionEvidenceLoop?: {\n readonly totalEvidence: number;\n readonly verdictCounts: {\n readonly pass: number;\n readonly fail: number;\n readonly blocked: number;\n readonly skipped: number;\n };\n readonly reviewerConflictCounts: {\n readonly execution_fail_reviewer_approved: number;\n readonly execution_pass_reviewer_rejected: number;\n };\n readonly tmsAdapterCounts: Readonly<Partial<Record<string, number>>>;\n readonly distinctSigningKeyFingerprints: readonly string[];\n readonly earliestExecutedAt: string;\n readonly latestExecutedAt: string;\n };\n /**\n * Per-locale calibration health table (Issue #2188, W8-6).\n *\n * Optional and additive. Populated only when the dossier generator\n * finds at least one per-locale Platt-curve fixture under\n * `fixtures/test-intelligence/locale-calibration/<locale>/`.\n * Reflects the `G13_LOCALE_CALIBRATION_HEALTHY` gate per locale so\n * the audit-dossier renders a one-row-per-locale health summary\n * alongside the existing self-improving-calibration refit history.\n *\n * Legacy runs that do not ship per-locale fixtures keep the dossier\n * shape stable.\n */\n readonly localeCalibrationHealth?: {\n readonly gateCode: \"G13_LOCALE_CALIBRATION_HEALTHY\";\n readonly thresholds: {\n readonly kappaFloor: number;\n readonly eceCeiling: number;\n readonly minimumSampleCount: number;\n };\n readonly localeCount: number;\n readonly passedCount: number;\n readonly failedLocales: readonly string[];\n readonly rows: readonly {\n readonly locale: string;\n readonly heldOutKappa: number;\n readonly heldOutEce: number;\n readonly sampleCount: number;\n readonly fallbackToDefault: boolean;\n readonly passed: boolean;\n }[];\n };\n readonly regulatorCoverage: readonly AuditDossierRegulationCoverageEntry[];\n readonly summary: {\n readonly harnessVersion: string;\n readonly gitSha: string;\n readonly benchmarkProtocolVersion: string;\n readonly ictRegisterRefs: readonly string[];\n readonly policyProfileId: string;\n readonly modelCardId: string;\n readonly complianceFrameworkCount: number;\n readonly complianceAnnotationCount: number;\n readonly calibrationSampleCount: number;\n readonly localeCurveCount: number;\n readonly interRaterFailureCount: number;\n readonly driftFindingCount: number;\n readonly incidentCount: number;\n readonly subprocessorCount: number;\n readonly faithfulnessMismatchCount: number;\n readonly selfConsistencyTargetCount: number;\n readonly provenanceRoot: string;\n readonly provenanceLeafCount: number;\n readonly merkleProofSha256: string;\n readonly runId: string;\n };\n}\n\n/** Detached Ed25519 signature metadata stored alongside the manifest. */\nexport interface AuditDossierSignature {\n readonly schemaVersion: typeof AUDIT_DOSSIER_SIGNATURE_SCHEMA_VERSION;\n readonly algorithm: \"ed25519\";\n readonly keyFingerprintSha256: string;\n readonly publicKeyPem: string;\n readonly manifestSha256: string;\n readonly signatureBase64: string;\n}\n\n/**\n * Current contract version constant.\n * Must be bumped according to CONTRACT_CHANGELOG.md rules.\n * Package version alignment is documented in VERSIONING.md.\n */\nexport const CONTRACT_VERSION = \"4.66.0\" as const;\n\n// ---------------------------------------------------------------------------\n// Issue #1774 — UntrustedContentNormalizer (2025-vintage injection carriers).\n// ---------------------------------------------------------------------------\n\n/**\n * Filename of the canonical-JSON drop-count report emitted by the\n * untrusted-content normalizer at `<runDir>/`. The report carries\n * **drop counts only** — no raw stripped content is ever persisted.\n */\nexport const UNTRUSTED_CONTENT_NORMALIZATION_REPORT_ARTIFACT_FILENAME =\n \"untrusted-content-normalization-report.json\" as const;\n\n/** Schema version for the untrusted-content-normalization report. */\nexport const UNTRUSTED_CONTENT_NORMALIZATION_REPORT_SCHEMA_VERSION =\n \"1.0.0\" as const;\n\n/**\n * Per-element hard byte cap applied to individual untrusted text spans\n * (Figma TEXT layer characters, individual ADF inline runs). Reuses the\n * Jira comment-body cap as the baseline so the normalizer never persists\n * a single element larger than the smallest existing Jira ceiling.\n */\nexport const MAX_UNTRUSTED_CONTENT_ELEMENT_BYTES = 4_096 as const;\n\n/**\n * Hard cap on the UTF-8 byte length of any single Markdown body fed to\n * the normalizer. Matches `MAX_CUSTOM_CONTEXT_RAW_MARKDOWN_BYTES` so a\n * caller that bypassed the upstream custom-context cap cannot turn the\n * normalizer into a CPU-exhaustion vector.\n */\nexport const MAX_UNTRUSTED_CONTENT_MARKDOWN_BYTES = 32_768 as const;\n\n/**\n * Carrier kinds tracked by the untrusted-content normalizer. Each entry\n * corresponds to one drop-count counter in the report. Stable,\n * locale-independent strings safe to ship to automation.\n */\nexport const ALLOWED_UNTRUSTED_CONTENT_CARRIER_KINDS = [\n \"figma_hidden_layer\",\n \"figma_zero_opacity_layer\",\n \"figma_off_canvas_layer\",\n \"figma_zero_font_size_layer\",\n \"sentinel_layer_name\",\n \"zero_width_character\",\n \"adf_collapsed_node\",\n \"element_truncated\",\n \"pii_match\",\n \"secret_match\",\n \"markdown_injection_pattern\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_UNTRUSTED_CONTENT_CARRIER_KINDS}. */\nexport type UntrustedContentCarrierKind =\n (typeof ALLOWED_UNTRUSTED_CONTENT_CARRIER_KINDS)[number];\n\n/** Severity levels emitted alongside untrusted-content carrier counts. */\nexport const ALLOWED_UNTRUSTED_CONTENT_SEVERITIES = [\n \"info\",\n \"warning\",\n \"critical\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_UNTRUSTED_CONTENT_SEVERITIES}. */\nexport type UntrustedContentSeverity =\n (typeof ALLOWED_UNTRUSTED_CONTENT_SEVERITIES)[number];\n\n/** Outcome routing emitted by the normalizer. */\nexport const ALLOWED_UNTRUSTED_CONTENT_OUTCOMES = [\n \"ok\",\n \"needs_review\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_UNTRUSTED_CONTENT_OUTCOMES}. */\nexport type UntrustedContentNormalizationOutcome =\n (typeof ALLOWED_UNTRUSTED_CONTENT_OUTCOMES)[number];\n\n// ---------------------------------------------------------------------------\n// Issue #1778 — Cache-break detector (intent-suppressed, redacted diffs).\n// ---------------------------------------------------------------------------\n\n/**\n * Subdirectory under `<runDir>/` where the cache-break detector writes\n * canonical-JSON diff artifacts (one file per detected break). Diffs are\n * redacted via `UntrustedContentNormalizer` + `redactHighRiskSecrets`\n * before persistence — a poisoned tool result that broke the cache must\n * never be persisted raw.\n */\nexport const CACHE_BREAK_ARTIFACT_DIRECTORY =\n \"observability/cache-breaks\" as const;\n\n/** Schema version for the per-break diff artifact. */\nexport const CACHE_BREAK_DIFF_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Heuristic threshold: when the observed `cacheReadTokens` is below this\n * fraction of the expected baseline AND the new `cacheCreationTokens`\n * exceeds {@link CACHE_BREAK_MIN_CREATION_TOKENS}, the detector flags\n * the call as a cache break.\n */\nexport const CACHE_BREAK_READ_RATIO_THRESHOLD = 0.05 as const;\n\n/**\n * Heuristic threshold: minimum `cacheCreationTokens` for the heuristic to\n * fire. Pairs with {@link CACHE_BREAK_READ_RATIO_THRESHOLD}; ensures a\n * cold-start small re-prompt does not register as a break.\n */\nexport const CACHE_BREAK_MIN_CREATION_TOKENS = 2_000 as const;\n\n/**\n * Maximum number of per-`querySource` snapshots retained by the LRU\n * inside the detector. Keeps the working set bounded under bursty\n * multi-source jobs.\n */\nexport const CACHE_BREAK_DETECTOR_MAX_SNAPSHOTS = 10 as const;\n\n/**\n * Closed set of suppression reasons recognised by\n * `notifyCompaction` / `notifyCacheDeletion`. Stable, locale-independent\n * strings safe to ship to automation.\n */\nexport const ALLOWED_CACHE_BREAK_SUPPRESSION_REASONS = [\n \"compaction\",\n \"cache_deletion\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_CACHE_BREAK_SUPPRESSION_REASONS}. */\nexport type CacheBreakSuppressionReason =\n (typeof ALLOWED_CACHE_BREAK_SUPPRESSION_REASONS)[number];\n\n// ---------------------------------------------------------------------------\n// Issue #1803 — release-pipeline integration with consolidated readiness report.\n// ---------------------------------------------------------------------------\n\n/**\n * Filename of the canonical-JSON release-readiness report committed to\n * evidence at `<RELEASE_READINESS_ARTIFACT_DIRECTORY>/release-readiness-report.json`.\n *\n * Issue #1803: the consolidated single-command output of the release pipeline\n * (`release:quality-gates`). The orchestrator runs the twelve canonical\n * release-pipeline gates as subprocesses, captures per-gate logs to disk,\n * and writes this report referencing each log so a CI failure attributes\n * directly to the offending gate.\n */\nexport const RELEASE_READINESS_REPORT_ARTIFACT_FILENAME =\n \"release-readiness-report.json\" as const;\n\n/** Directory where the consolidated readiness report is committed to evidence. */\nexport const RELEASE_READINESS_ARTIFACT_DIRECTORY =\n \"evidence/release-readiness\" as const;\n\n/** Schema version for the canonical-JSON release-readiness report. */\nexport const RELEASE_READINESS_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/**\n * Closed list of release-readiness gate identifiers, in the canonical\n * pipeline order from Issue #1803. The orchestrator MUST run gates in this\n * exact order; the report MUST list verdicts in this exact order.\n *\n * The list is closed: the parser refuses any payload with unknown gate ids,\n * duplicates, or missing entries — the consolidated report cannot silently\n * skip a gate.\n */\nexport const ALLOWED_RELEASE_READINESS_GATE_IDS = [\n \"typecheck\",\n \"test\",\n \"test_ti_eval\",\n \"test_ti_live_e2e\",\n \"lint_no_telemetry\",\n \"lint_secrets_all\",\n \"lint_agent_boundaries\",\n \"lint_ts_style\",\n \"build\",\n \"release_ml_bom_emit\",\n \"release_merkle_roundtrip\",\n \"release_library_coverage_report\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_RELEASE_READINESS_GATE_IDS}. */\nexport type ReleaseReadinessGateId =\n (typeof ALLOWED_RELEASE_READINESS_GATE_IDS)[number];\n\n/**\n * Closed list of per-gate statuses recognised by the consolidated\n * release-readiness report. `skipped` records intentional opt-outs (e.g.\n * `test_ti_live_e2e` when live-credentials are absent and the gate is\n * declared opt-in) without polluting `failed` attribution.\n */\nexport const ALLOWED_RELEASE_READINESS_GATE_STATUSES = [\n \"passed\",\n \"failed\",\n \"skipped\",\n] as const;\n\n/** Discriminated alias for {@link ALLOWED_RELEASE_READINESS_GATE_STATUSES}. */\nexport type ReleaseReadinessGateStatus =\n (typeof ALLOWED_RELEASE_READINESS_GATE_STATUSES)[number];\n\n/**\n * Per-gate result row in the consolidated release-readiness report.\n *\n * - `command` records the exact pnpm script invocation (e.g.\n * `\"pnpm run lint:ts-style\"`) so the report is reproducible offline.\n * - `exitCode` is `0` for `passed`, non-zero for `failed`, and `null` for\n * `skipped` (no subprocess ran).\n * - `durationMs` is wall-clock duration in milliseconds; `0` for skipped\n * gates.\n * - `logPath` is repo-relative path to the captured stdout+stderr log,\n * so the consolidated report links each failure to its evidence.\n * `null` for skipped gates.\n * - `attribution` carries short, locale-independent labels surfaced from\n * the gate (e.g. `\"merkle_chain_break\"`, `\"ml_bom_hash_mismatch\"`); the\n * release-pipeline runner forwards them verbatim.\n */\nexport interface ReleaseReadinessGateResult {\n readonly gateId: ReleaseReadinessGateId;\n readonly command: string;\n readonly status: ReleaseReadinessGateStatus;\n readonly exitCode: number | null;\n readonly durationMs: number;\n readonly logPath: string | null;\n readonly attribution: readonly string[];\n}\n\n/**\n * Consolidated release-readiness report (Issue #1803).\n *\n * `gates[]` MUST list one entry per `ALLOWED_RELEASE_READINESS_GATE_IDS`\n * member, in canonical order. `passed` is `true` iff every non-skipped\n * gate passed.\n */\nexport interface ReleaseReadinessReport {\n readonly schemaVersion: typeof RELEASE_READINESS_REPORT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly releaseId: string;\n readonly generatedAt: string;\n readonly passed: boolean;\n readonly gates: readonly ReleaseReadinessGateResult[];\n}\n\n// ---------------------------------------------------------------------------\n// Incident-handling surface (Issue #2114, DORA Art. 10).\n// ---------------------------------------------------------------------------\n\n/** Schema version stamped on every persisted `incidents.json`. */\nexport const INCIDENT_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the per-job incident-handling artifact. */\nexport const INCIDENT_REPORT_ARTIFACT_FILENAME = \"incidents.json\" as const;\n\n/** Severity bands recognized by the incident classifier. */\nexport const ALLOWED_INCIDENT_SEVERITIES = [\n \"low\",\n \"medium\",\n \"high\",\n \"critical\",\n] as const;\nexport type IncidentSeverity = (typeof ALLOWED_INCIDENT_SEVERITIES)[number];\n\n/**\n * Closed enumeration of incident categories as defined by Issue #2114.\n * Listed in canonical sort order; new categories require a contract bump.\n */\nexport const ALLOWED_INCIDENT_CATEGORIES = [\n \"compliance_rule_pack_violation\",\n \"drift_alert\",\n \"judge_disagreement_persistent\",\n \"pii_leakage\",\n \"policy_gate_bypass\",\n \"replay_cache_miss_unexpected\",\n \"subprocessor_outage\",\n] as const;\nexport type IncidentCategory = (typeof ALLOWED_INCIDENT_CATEGORIES)[number];\n\n/**\n * Pipeline-level incident review state. When the classifier emits any\n * `critical` event, the report stamps `incident_ack_required` and the\n * pipeline pauses until an operator records a manual acknowledgement.\n */\nexport const ALLOWED_INCIDENT_REVIEW_STATES = [\n \"ok\",\n \"incident_ack_required\",\n] as const;\nexport type IncidentReviewState =\n (typeof ALLOWED_INCIDENT_REVIEW_STATES)[number];\n\n/**\n * Reference to a persisted artifact backing an incident's evidence\n * trail. Lets a sink cite an artifact without depending on the full\n * Wave 1 evidence-manifest type.\n */\nexport interface ManifestRef {\n readonly filename: string;\n readonly sha256: string;\n}\n\n/** Single classified incident emitted to the operator's `IncidentSink`. */\nexport interface IncidentEvent {\n readonly id: string;\n readonly severity: IncidentSeverity;\n readonly category: IncidentCategory;\n readonly observedAt: string;\n readonly jobId: string;\n readonly evidence: readonly ManifestRef[];\n readonly rootCauseHypothesis: string;\n}\n\n/**\n * Persisted incident-handling envelope written per job. The order of\n * `events` is canonical: severity-rank descending, then category, then\n * `id`, so byte-identical inputs always produce identical files.\n */\nexport interface IncidentReport {\n readonly schemaVersion: typeof INCIDENT_REPORT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n readonly jobId: string;\n readonly generatedAt: string;\n readonly reviewState: IncidentReviewState;\n readonly events: readonly IncidentEvent[];\n}\n\n// ---------------------------------------------------------------------------\n// Production-grade TMS adapter surface (Issue #2183, Wave 8).\n// ---------------------------------------------------------------------------\n\n/**\n * TMS adapter discriminator for the four enterprise TMS systems shipped\n * in Wave 8 (Issue #2183). The order is canonical for sort-based\n * registry rendering; new adapters extend the array.\n */\nexport const ALLOWED_TMS_ADAPTER_IDS = [\n \"alm\",\n \"polarion\",\n \"qtest\",\n \"xray\",\n] as const;\nexport type TmsAdapterId = (typeof ALLOWED_TMS_ADAPTER_IDS)[number];\n\n/**\n * Per-case push verdict surfaced on the persisted `tms-push-report.json`.\n *\n * - `pushed` — the adapter created a fresh test case in the TMS.\n * - `skipped-dup` — idempotency lookup found a prior write under the\n * same `(tenantId, runId, testCaseId)` key. No write performed.\n * - `failed` — adapter or transport error after all retry attempts.\n */\nexport const ALLOWED_TMS_PUSH_VERDICTS = [\n \"pushed\",\n \"skipped-dup\",\n \"failed\",\n] as const;\nexport type TmsPushVerdict = (typeof ALLOWED_TMS_PUSH_VERDICTS)[number];\n\n/**\n * Authentication kinds accepted by the TMS adapter contract. Issue #2183\n * requires PAT (Personal Access Token), OAuth 2.0 bearer, and generic\n * Bearer token. Each adapter advertises which kind(s) it supports.\n */\nexport const ALLOWED_TMS_AUTH_KINDS = [\"pat\", \"oauth2\", \"bearer\"] as const;\nexport type TmsAuthKind = (typeof ALLOWED_TMS_AUTH_KINDS)[number];\n\n/** Schema version stamped on every persisted `tms-push-report.json`. */\nexport const TMS_PUSH_REPORT_SCHEMA_VERSION = \"1.0.0\" as const;\n\n/** Canonical filename for the per-push artifact. */\nexport const TMS_PUSH_REPORT_ARTIFACT_FILENAME =\n \"tms-push-report.json\" as const;\n\n/**\n * Allowed reasons the push pipeline may refuse to perform any write.\n * Evaluated in fail-closed order; every fired refusal is recorded so\n * operators can address them all in one cycle.\n */\nexport const ALLOWED_TMS_PUSH_REFUSAL_CODES = [\n \"credentials_missing\",\n \"credentials_invalid\",\n \"project_validation_failed\",\n \"mapping_preview_missing\",\n \"mapping_preview_unreadable\",\n \"no_mapped_test_cases\",\n \"adapter_unsupported\",\n \"connect_failed\",\n] as const;\nexport type TmsPushRefusalCode =\n (typeof ALLOWED_TMS_PUSH_REFUSAL_CODES)[number];\n\n/** Single per-case row in `tms-push-report.json`. */\nexport interface TmsPushReportEntry {\n /** Stable provider-neutral id from `qc-mapping-preview.json`. */\n testCaseId: string;\n /** Idempotency key derived from `(tenantId, runId, testCaseId)`. */\n idempotencyKey: string;\n /** Push verdict; one of `ALLOWED_TMS_PUSH_VERDICTS`. */\n verdict: TmsPushVerdict;\n /**\n * Round-trip evidence: the TMS-assigned id when the verdict is\n * `pushed` or `skipped-dup`. Empty string for `failed`.\n */\n tmsTestCaseId: string;\n /**\n * Sanitised TMS error code preserved on `failed` (e.g. ALM\n * `qccore.entity.not-found`, Xray `JIRA-401`). Empty for non-failures.\n */\n tmsErrorCode: string;\n /**\n * Length-bounded, redacted TMS error message preserved on `failed`.\n * Never carries URLs, tokens, or raw response bodies. Empty for\n * non-failures.\n */\n tmsErrorMessage: string;\n /** Number of HTTP attempts performed for this case (1 + retries). */\n attemptCount: number;\n /** ISO-8601 UTC timestamp at which the verdict was recorded. */\n recordedAt: string;\n}\n\n/**\n * Aggregate `tms-push-report.json` artifact (Issue #2183).\n *\n * One file per push operation written under the run dir. The artifact is\n * the round-trip evidence that an operator hands an auditor: every\n * approved+mapped case appears exactly once with a verdict and (for\n * pushed cases) the TMS-assigned id.\n */\nexport interface TmsPushReportArtifact {\n readonly schemaVersion: typeof TMS_PUSH_REPORT_SCHEMA_VERSION;\n readonly contractVersion: typeof TEST_INTELLIGENCE_CONTRACT_VERSION;\n /** Adapter discriminator used by the run. */\n readonly adapterId: TmsAdapterId;\n /** Adapter implementation version stamped at compile time. */\n readonly adapterVersion: string;\n /**\n * Symbolic alias for the TMS endpoint (never the resolved URL).\n * Mirrors `QcMappingProfile.baseUrlAlias`.\n */\n readonly tmsEndpointAlias: string;\n /** Project id inside the TMS (e.g. Jira project key, ALM project name). */\n readonly tmsProjectId: string;\n /** Run identifier from the source `run-dir`; empty when not derivable. */\n readonly runId: string;\n /** Stable tenant id used in idempotency keys. */\n readonly tenantId: string;\n /** ISO-8601 UTC timestamp at which the push run completed. */\n readonly generatedAt: string;\n /** Whether the pipeline refused to perform any write. */\n readonly refused: boolean;\n /** Sorted, deduplicated refusal codes that fired. */\n readonly refusalCodes: readonly TmsPushRefusalCode[];\n /** Whether the push was a `--dry-run` (no actual TMS writes performed). */\n readonly dryRun: boolean;\n /** Per-case results, sorted by `testCaseId`. */\n readonly entries: readonly TmsPushReportEntry[];\n /** Number of entries with verdict `pushed`. */\n readonly pushedCount: number;\n /** Number of entries with verdict `skipped-dup`. */\n readonly skippedDuplicateCount: number;\n /** Number of entries with verdict `failed`. */\n readonly failedCount: number;\n /** Hard invariant: the adapter never embeds raw screenshots. */\n readonly rawScreenshotsIncluded: false;\n /** Hard invariant: the adapter never embeds credentials. */\n readonly credentialsIncluded: false;\n /** Hard invariant: the adapter never echoes resolved URLs. */\n readonly transferUrlIncluded: false;\n}\n","/**\n * Standalone Test Intelligence HTTP runtime constants.\n *\n * This module is the single source of truth for runtime defaults consumed by\n * `packages/server/src/server.ts`, `packages/server/src/request-handler.ts`,\n * and the operator CLI (#20). Operator-facing environment-variable names use\n * the Test Intelligence namespace.\n *\n * `DEFAULT_OUTPUT_ROOT` persists per-job artifacts under `.test-intelligence`\n * to match the product name.\n */\n\nimport {\n TEST_INTELLIGENCE_ENV,\n TEST_INTELLIGENCE_MULTISOURCE_ENV,\n} from \"@oscharko-dev/ti-contracts\";\n\n/** Default bind host for the standalone server. Loopback-only by default. */\nexport const DEFAULT_HOST: string = \"127.0.0.1\";\n\n/** Default TCP port for the standalone server. */\nexport const DEFAULT_PORT: number = 1983;\n\n/** Default per-job artefact root directory (operator-overridable). */\nexport const DEFAULT_OUTPUT_ROOT: string = \".test-intelligence\";\n\n/** Default per-IP+route rate limit (requests per minute). */\nexport const DEFAULT_RATE_LIMIT_PER_MINUTE: number = 60;\n\n/** Width of the rate-limit fixed window (ms). */\nexport const RATE_LIMIT_WINDOW_MS: number = 60_000;\n\n/**\n * Maximum JSON request body size for normal write routes (1 MiB). Larger\n * bodies fail with `413 PAYLOAD_TOO_LARGE` before any parsing.\n */\nexport const MAX_REQUEST_BODY_BYTES: number = 1_048_576;\n\n/**\n * Maximum body size for the run-submission route which carries an inline\n * Figma node tree (8 MiB).\n */\nexport const MAX_SUBMIT_BODY_BYTES: number = 8_388_608;\n\n/** Environment-variable name controlling HSTS opt-in. */\nexport const ENABLE_HSTS_ENV: string = \"TEST_INTELLIGENCE_ENABLE_HSTS\";\n\n/** Default `Strict-Transport-Security` header when HSTS is enabled. */\nexport const DEFAULT_STRICT_TRANSPORT_SECURITY: string = \"max-age=31536000\";\n\n/**\n * Restrictive default Content-Security-Policy applied to every response.\n * The standalone server is API-only, so `default-src 'self'` and\n * `object-src 'none'` are safe.\n */\nexport const DEFAULT_CONTENT_SECURITY_POLICY: string =\n \"default-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'\";\n\n/** Public API route prefix for every TI HTTP endpoint. */\nexport const API_ROUTE_PREFIX: string = \"/api/v1\";\n\n/**\n * Resolve the boolean test-intelligence startup gate from the environment.\n * The Test Intelligence env var is the only supported startup gate.\n */\nexport const resolveTestIntelligenceEnabled = (\n env: NodeJS.ProcessEnv = process.env,\n): boolean => {\n return parseBooleanEnv(env[TEST_INTELLIGENCE_ENV]);\n};\n\n/**\n * Resolve the nested multi-source ingestion gate (#1431). Callers MUST\n * verify {@link resolveTestIntelligenceEnabled} before consulting this\n * resolver.\n */\nexport const resolveTestIntelligenceMultiSourceEnvEnabled = (\n env: NodeJS.ProcessEnv = process.env,\n): boolean => {\n return parseBooleanEnv(env[TEST_INTELLIGENCE_MULTISOURCE_ENV]);\n};\n\n/**\n * Resolve the `Strict-Transport-Security` value for the current process.\n * Returns `undefined` when HSTS is not requested (the default — the\n * standalone server is loopback-only in the typical configuration and\n * setting HSTS on `http://127.0.0.1` would be a misconfiguration).\n */\nexport const resolveStrictTransportSecurity = (\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined => {\n const raw = env[ENABLE_HSTS_ENV];\n if (raw === undefined) {\n return undefined;\n }\n const normalized = raw.trim().toLowerCase();\n if (\n normalized === \"\" ||\n normalized === \"0\" ||\n normalized === \"false\" ||\n normalized === \"no\" ||\n normalized === \"off\"\n ) {\n return undefined;\n }\n return DEFAULT_STRICT_TRANSPORT_SECURITY;\n};\n\nconst parseBooleanEnv = (raw: string | undefined): boolean => {\n if (raw === undefined) {\n return false;\n }\n const normalized = raw.trim().toLowerCase();\n return (\n normalized === \"1\" ||\n normalized === \"true\" ||\n normalized === \"yes\" ||\n normalized === \"on\"\n );\n};\n","/**\n * Stable error-envelope codes for every standalone HTTP route.\n *\n * The string union is intentionally narrow: callers MUST emit one of these\n * codes so the operator-facing CLI, OpenAPI document, and audit log share a\n * single closed enumeration. Adding a code requires:\n * 1. extending {@link TestIntelligenceErrorCode},\n * 2. updating the OpenAPI route metadata,\n * 3. covering the new code in a route test.\n */\n\nexport type TestIntelligenceErrorCode =\n | \"BAD_REQUEST\"\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN_REQUEST_ORIGIN\"\n | \"PAYLOAD_TOO_LARGE\"\n | \"UNSUPPORTED_MEDIA_TYPE\"\n | \"RATE_LIMITED\"\n | \"NOT_FOUND\"\n | \"METHOD_NOT_ALLOWED\"\n | \"FEATURE_GATE_DISABLED\"\n | \"AUTHENTICATION_UNAVAILABLE\"\n | \"LLM_GATEWAY_UNCONFIGURED\"\n | \"LLM_GATEWAY_FAILED\"\n | \"BEARER_TOKEN_MISSING\"\n | \"VERIFICATION_FAILED\"\n | \"INTERNAL_ERROR\";\n\nexport interface TestIntelligenceErrorEnvelope {\n readonly error: TestIntelligenceErrorCode;\n readonly message: string;\n}\n\n/**\n * Map an error code to the canonical HTTP status. Routes that need to deviate\n * (e.g. a `VERIFICATION_FAILED` returned with 200 when the operator asked for\n * `report-only`) construct the envelope manually.\n */\nexport const statusForErrorCode = (code: TestIntelligenceErrorCode): number => {\n switch (code) {\n case \"BAD_REQUEST\":\n return 400;\n case \"UNAUTHORIZED\":\n case \"BEARER_TOKEN_MISSING\":\n return 401;\n case \"FORBIDDEN_REQUEST_ORIGIN\":\n case \"FEATURE_GATE_DISABLED\":\n return 403;\n case \"NOT_FOUND\":\n return 404;\n case \"METHOD_NOT_ALLOWED\":\n return 405;\n case \"PAYLOAD_TOO_LARGE\":\n return 413;\n case \"UNSUPPORTED_MEDIA_TYPE\":\n return 415;\n case \"RATE_LIMITED\":\n return 429;\n case \"INTERNAL_ERROR\":\n case \"VERIFICATION_FAILED\":\n return 500;\n case \"AUTHENTICATION_UNAVAILABLE\":\n case \"LLM_GATEWAY_UNCONFIGURED\":\n case \"LLM_GATEWAY_FAILED\":\n return 503;\n }\n};\n","/**\n * Transport-level helpers used by every route handler.\n *\n * Three concerns live here:\n * 1. Reading and bounding JSON request bodies (`readJsonBody`).\n * 2. Writing JSON / SSE responses with a uniform security-header set\n * (`writeJsonResponse`, `writeErrorResponse`, `writeSseFrame`,\n * `writeSseRetry`).\n * 3. Reading the client identity used by the rate limiter\n * (`resolveClientKey`).\n *\n * Every response goes through {@link applySecurityHeaders} so the CSP, HSTS,\n * X-Content-Type-Options, and Referrer-Policy posture is consistent.\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport {\n DEFAULT_CONTENT_SECURITY_POLICY,\n resolveStrictTransportSecurity,\n} from \"./constants.js\";\nimport {\n statusForErrorCode,\n type TestIntelligenceErrorCode,\n type TestIntelligenceErrorEnvelope,\n} from \"./error-codes.js\";\n\nexport interface SecurityHeaderOptions {\n readonly strictTransportSecurity?: string | undefined;\n readonly contentSecurityPolicy?: string;\n}\n\nexport const applySecurityHeaders = (\n response: ServerResponse,\n options: SecurityHeaderOptions = {},\n): void => {\n response.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n response.setHeader(\"Referrer-Policy\", \"no-referrer\");\n response.setHeader(\n \"Content-Security-Policy\",\n options.contentSecurityPolicy ?? DEFAULT_CONTENT_SECURITY_POLICY,\n );\n const hsts =\n options.strictTransportSecurity ?? resolveStrictTransportSecurity();\n if (hsts !== undefined) {\n response.setHeader(\"Strict-Transport-Security\", hsts);\n }\n};\n\nexport const writeJsonResponse = ({\n response,\n statusCode,\n payload,\n extraHeaders,\n}: {\n response: ServerResponse;\n statusCode: number;\n payload: unknown;\n extraHeaders?: Readonly<Record<string, string>>;\n}): void => {\n applySecurityHeaders(response);\n response.setHeader(\"Content-Type\", \"application/json; charset=utf-8\");\n if (extraHeaders !== undefined) {\n for (const [name, value] of Object.entries(extraHeaders)) {\n response.setHeader(name, value);\n }\n }\n response.statusCode = statusCode;\n response.end(JSON.stringify(payload));\n};\n\nexport const writeErrorResponse = ({\n response,\n code,\n message,\n extraHeaders,\n statusCode,\n}: {\n response: ServerResponse;\n code: TestIntelligenceErrorCode;\n message: string;\n extraHeaders?: Readonly<Record<string, string>>;\n statusCode?: number;\n}): void => {\n const envelope: TestIntelligenceErrorEnvelope = { error: code, message };\n writeJsonResponse({\n response,\n statusCode: statusCode ?? statusForErrorCode(code),\n payload: envelope,\n ...(extraHeaders !== undefined ? { extraHeaders } : {}),\n });\n};\n\nexport interface ReadJsonBodyOk {\n readonly ok: true;\n readonly value: unknown;\n}\n\nexport interface ReadJsonBodyErr {\n readonly ok: false;\n readonly code: \"PAYLOAD_TOO_LARGE\" | \"BAD_REQUEST\";\n readonly message: string;\n}\n\nexport type ReadJsonBodyResult = ReadJsonBodyOk | ReadJsonBodyErr;\n\nexport const readJsonBody = async ({\n request,\n maxBytes,\n}: {\n request: IncomingMessage;\n maxBytes: number;\n}): Promise<ReadJsonBodyResult> => {\n const chunks: Buffer[] = [];\n let received = 0;\n\n for await (const chunk of request) {\n const buf =\n typeof chunk === \"string\"\n ? Buffer.from(chunk, \"utf8\")\n : (chunk as Buffer);\n received += buf.length;\n if (received > maxBytes) {\n return {\n ok: false,\n code: \"PAYLOAD_TOO_LARGE\",\n message: `Request body exceeds ${maxBytes} bytes.`,\n };\n }\n chunks.push(buf);\n }\n\n if (chunks.length === 0) {\n return {\n ok: false,\n code: \"BAD_REQUEST\",\n message: \"Request body is empty.\",\n };\n }\n\n const text = Buffer.concat(chunks).toString(\"utf8\");\n try {\n return { ok: true, value: JSON.parse(text) as unknown };\n } catch {\n return {\n ok: false,\n code: \"BAD_REQUEST\",\n message: \"Request body is not valid JSON.\",\n };\n }\n};\n\nexport interface SseWriter {\n writeEvent(input: { event: string; data: unknown; id?: string }): void;\n writeRetry(retryMs: number): void;\n end(): void;\n}\n\nexport const beginSseResponse = (\n response: ServerResponse,\n options: SecurityHeaderOptions = {},\n): SseWriter => {\n applySecurityHeaders(response, options);\n response.setHeader(\"Content-Type\", \"text/event-stream; charset=utf-8\");\n response.setHeader(\"Cache-Control\", \"no-cache, no-transform\");\n response.setHeader(\"Connection\", \"keep-alive\");\n response.statusCode = 200;\n response.flushHeaders();\n\n return {\n writeEvent({ event, data, id }) {\n const lines: string[] = [];\n if (id !== undefined) {\n lines.push(`id: ${id}`);\n }\n lines.push(`event: ${event}`);\n const serialized = JSON.stringify(data);\n // `JSON.stringify` with no spacing never emits a literal newline, so\n // the payload is always a single `data:` line. We still split on `\\n`\n // to remain spec-compliant if a caller ever pre-serializes with\n // embedded newlines (the SSE spec requires multi-line splitting).\n for (const line of serialized.split(\"\\n\")) {\n lines.push(`data: ${line}`);\n }\n lines.push(\"\", \"\");\n response.write(lines.join(\"\\n\"));\n },\n writeRetry(retryMs) {\n response.write(`retry: ${String(retryMs)}\\n\\n`);\n },\n end() {\n response.end();\n },\n };\n};\n\nconst FORWARDED_FOR_HEADER = \"x-forwarded-for\";\n\n/**\n * Resolve the rate-limit client key. Prefers the first hop in\n * `X-Forwarded-For` when present (so a reverse-proxy deployment scopes\n * limits per real client) and falls back to the socket remote address.\n */\nexport const resolveClientKey = (request: IncomingMessage): string => {\n const forwarded = request.headers[FORWARDED_FOR_HEADER];\n const raw = Array.isArray(forwarded) ? forwarded[0] : forwarded;\n if (typeof raw === \"string\" && raw.length > 0) {\n const first = raw.split(\",\")[0]?.trim();\n if (first !== undefined && first.length > 0) {\n return first;\n }\n }\n return request.socket.remoteAddress ?? \"unknown\";\n};\n","/**\n * Operational observability for the standalone HTTP runtime.\n *\n * Three sinks are exposed:\n * 1. {@link createRequestLogger} — structured per-request log records\n * flowing through the existing {@link WorkspaceRuntimeLogger}. Each\n * record carries method, path, status, duration, and request ID.\n * 2. {@link createAuditLogger} — append-only audit lines for governance\n * events (review decisions, evidence-verify outcomes, TMS pushes).\n * Audit lines are JSON, one per line, and never leave the process.\n * 3. {@link createIncidentReporter} — adapter from operational warnings\n * to the in-process `IncidentSink` (see incident-sink.ts).\n *\n * Consistent with the product's zero-telemetry posture, none of these\n * sinks open a network socket. All output is local: stdout/stderr for the\n * request logger by default, an operator-supplied writer for the audit\n * log, and an operator-supplied `IncidentSink` for the incident reporter.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { WorkspaceRuntimeLogger } from \"@oscharko-dev/ti-security\";\n\nexport interface RequestObservation {\n readonly requestId: string;\n readonly method: string;\n readonly path: string;\n readonly statusCode: number;\n readonly durationMs: number;\n readonly clientKey: string;\n readonly route?: string;\n}\n\nexport interface RequestLogger {\n log(observation: RequestObservation): void;\n newRequestId(): string;\n}\n\nexport const createRequestLogger = (\n logger: WorkspaceRuntimeLogger,\n): RequestLogger => {\n return {\n log({ requestId, method, path, statusCode, durationMs, clientKey, route }) {\n const segments = [\n `method=${method}`,\n `path=${path}`,\n `status=${String(statusCode)}`,\n `durationMs=${String(durationMs)}`,\n `client=${clientKey}`,\n ];\n if (route !== undefined) {\n segments.push(`route=${route}`);\n }\n logger.log({\n level: statusCode >= 500 ? \"error\" : \"info\",\n message: segments.join(\" \"),\n requestId,\n method,\n path,\n statusCode,\n });\n },\n newRequestId() {\n return randomUUID();\n },\n };\n};\n\nexport interface AuditEvent {\n readonly action: string;\n readonly subject: string;\n readonly outcome: \"ok\" | \"denied\" | \"failed\";\n readonly principal?: string;\n readonly requestId?: string;\n readonly details?: Readonly<Record<string, string | number | boolean>>;\n}\n\nexport interface AuditLogger {\n record(event: AuditEvent): void;\n}\n\nexport interface CreateAuditLoggerInput {\n readonly write: (line: string) => void;\n readonly now?: () => string;\n}\n\nexport const createAuditLogger = ({\n write,\n now = () => new Date().toISOString(),\n}: CreateAuditLoggerInput): AuditLogger => {\n return {\n record(event) {\n const line = JSON.stringify({\n ts: now(),\n action: event.action,\n subject: event.subject,\n outcome: event.outcome,\n ...(event.principal !== undefined\n ? { principal: event.principal }\n : {}),\n ...(event.requestId !== undefined\n ? { requestId: event.requestId }\n : {}),\n ...(event.details !== undefined ? { details: event.details } : {}),\n });\n write(`${line}\\n`);\n },\n };\n};\n\nexport interface IncidentReporter {\n warn(event: { code: string; message: string; requestId?: string }): void;\n fail(event: { code: string; message: string; requestId?: string }): void;\n}\n\nexport const createIncidentReporter = (\n logger: WorkspaceRuntimeLogger,\n): IncidentReporter => {\n return {\n warn(event) {\n logger.log({\n level: \"warn\",\n message: `incident:${event.code} ${event.message}`,\n ...(event.requestId !== undefined\n ? { requestId: event.requestId }\n : {}),\n });\n },\n fail(event) {\n logger.log({\n level: \"error\",\n message: `incident:${event.code} ${event.message}`,\n ...(event.requestId !== undefined\n ? { requestId: event.requestId }\n : {}),\n });\n },\n };\n};\n","/**\n * Per-key fixed-window counters used by the request-handler rate limiter.\n *\n * The store is deliberately simple: a `Map` of bucket records, no LRU, no\n * background reaping. The handler reaps lazily on every read by dropping\n * any bucket whose window has expired. A long-running operator instance\n * with a high cardinality of remote IPs will trim memory each call without\n * needing a timer.\n */\n\nexport interface RateLimitBucket {\n count: number;\n windowStartedAtMs: number;\n}\n\nexport class RateLimitStore {\n private readonly buckets: Map<string, RateLimitBucket> = new Map();\n\n get(key: string): RateLimitBucket | undefined {\n return this.buckets.get(key);\n }\n\n set(key: string, bucket: RateLimitBucket): void {\n this.buckets.set(key, bucket);\n }\n\n delete(key: string): void {\n this.buckets.delete(key);\n }\n\n clear(): void {\n this.buckets.clear();\n }\n\n size(): number {\n return this.buckets.size;\n }\n}\n","/**\n * Fixed-window per-client+route rate limiter.\n *\n * The limiter is deterministic in the test suite because the `now` clock is\n * injected. Each `(clientKey, routeKey)` pair gets its own bucket; the\n * window is the {@link RATE_LIMIT_WINDOW_MS} sliding-fixed-window pair\n * (i.e. a new window starts the first time a request lands after the prior\n * window expired). A burst of N requests inside one window is accepted up\n * to `requestsPerMinute` and rejected with `429 RATE_LIMITED` thereafter.\n *\n * The store is intentionally not LRU: operators bind the standalone server\n * to a loopback interface, so the cardinality of `clientKey` is bounded by\n * the number of operator sessions. A future deployment with a public bind\n * can wrap {@link RateLimitStore} with an eviction policy without changing\n * this module.\n */\n\nimport { RATE_LIMIT_WINDOW_MS } from \"./constants.js\";\nimport { RateLimitStore } from \"./rate-limit-store.js\";\n\nexport interface RateLimitAllowed {\n readonly ok: true;\n readonly remaining: number;\n readonly resetAtMs: number;\n}\n\nexport interface RateLimitDenied {\n readonly ok: false;\n readonly retryAfterSeconds: number;\n readonly resetAtMs: number;\n}\n\nexport type RateLimitDecision = RateLimitAllowed | RateLimitDenied;\n\nexport interface RateLimiter {\n check(input: {\n clientKey: string;\n routeKey: string;\n nowMs: number;\n }): RateLimitDecision;\n reset(): void;\n}\n\nexport interface CreateRateLimiterInput {\n /** Maximum requests allowed within {@link RATE_LIMIT_WINDOW_MS}. */\n readonly requestsPerMinute: number;\n /** Optional injected store (defaults to a fresh in-memory map). */\n readonly store?: RateLimitStore;\n}\n\nexport const createRateLimiter = ({\n requestsPerMinute,\n store = new RateLimitStore(),\n}: CreateRateLimiterInput): RateLimiter => {\n if (!Number.isFinite(requestsPerMinute) || requestsPerMinute < 1) {\n throw new Error(\n \"createRateLimiter: requestsPerMinute must be a finite integer >= 1.\",\n );\n }\n const limit = Math.floor(requestsPerMinute);\n\n return {\n check({ clientKey, routeKey, nowMs }) {\n const key = `${clientKey}\\x00${routeKey}`;\n const existing = store.get(key);\n if (\n existing === undefined ||\n nowMs - existing.windowStartedAtMs >= RATE_LIMIT_WINDOW_MS\n ) {\n store.set(key, { count: 1, windowStartedAtMs: nowMs });\n return {\n ok: true,\n remaining: limit - 1,\n resetAtMs: nowMs + RATE_LIMIT_WINDOW_MS,\n };\n }\n\n if (existing.count >= limit) {\n const resetAtMs = existing.windowStartedAtMs + RATE_LIMIT_WINDOW_MS;\n return {\n ok: false,\n retryAfterSeconds: Math.max(1, Math.ceil((resetAtMs - nowMs) / 1000)),\n resetAtMs,\n };\n }\n\n existing.count += 1;\n return {\n ok: true,\n remaining: Math.max(0, limit - existing.count),\n resetAtMs: existing.windowStartedAtMs + RATE_LIMIT_WINDOW_MS,\n };\n },\n reset() {\n store.clear();\n },\n };\n};\n","/**\n * Per-request transport-security helpers.\n *\n * Two boundaries live here:\n * 1. Bearer-token validation (constant-time over sha256 digests).\n * 2. Same-origin / Content-Type validation for write routes.\n *\n * Production handlers never compare bearer tokens directly; they always go\n * through {@link validateBearerToken} so the timing-safe path is the only\n * path. Missing operator configuration (`bearerToken === undefined`)\n * intentionally returns `503 AUTHENTICATION_UNAVAILABLE` rather than `401`\n * so an unconfigured deployment can be distinguished from a wrong-token\n * client.\n */\n\nimport { createHash, timingSafeEqual } from \"node:crypto\";\nimport type { IncomingMessage } from \"node:http\";\n\nconst JSON_CONTENT_TYPE_PATTERN = /^application\\/json(?:\\s*(?:;|$))/i;\nconst ALLOWED_SEC_FETCH_SITE_VALUES = new Set([\"same-origin\", \"same-site\"]);\nconst TEST_INTELLIGENCE_BEARER_REALM = \"test-intelligence\";\n\nexport interface RequestSecurityOk {\n readonly ok: true;\n}\n\nexport interface RequestSecurityErr {\n readonly ok: false;\n readonly statusCode: number;\n readonly payload: {\n readonly error: \"FORBIDDEN_REQUEST_ORIGIN\" | \"UNSUPPORTED_MEDIA_TYPE\";\n readonly message: string;\n };\n}\n\nexport type RequestSecurityResult = RequestSecurityOk | RequestSecurityErr;\n\nexport interface BearerAuthOk {\n readonly ok: true;\n readonly principal: { readonly scheme: \"bearer\" };\n}\n\nexport interface BearerAuthErr {\n readonly ok: false;\n readonly statusCode: 401 | 503;\n readonly payload: {\n readonly error: \"UNAUTHORIZED\" | \"AUTHENTICATION_UNAVAILABLE\";\n readonly message: string;\n };\n readonly wwwAuthenticate?: string;\n}\n\nexport type BearerAuthResult = BearerAuthOk | BearerAuthErr;\n\nconst getHeaderValue = (\n value: string | string[] | undefined,\n): string | undefined => {\n if (typeof value === \"string\") {\n return value;\n }\n if (Array.isArray(value)) {\n return value[0];\n }\n return undefined;\n};\n\nconst normalizeOrigin = (value: string | undefined): string | undefined => {\n if (value === undefined || value.trim().length === 0) {\n return undefined;\n }\n try {\n return new URL(value).origin;\n } catch {\n return undefined;\n }\n};\n\nconst normalizeOriginHost = (host: string): string => {\n if (host.includes(\":\") && !host.startsWith(\"[\")) {\n return `[${host}]`;\n }\n return host;\n};\n\nconst isLoopbackLikeHost = (host: string): boolean => {\n const normalized = host.trim().toLowerCase();\n return (\n normalized === \"127.0.0.1\" ||\n normalized === \"localhost\" ||\n normalized === \"::1\" ||\n normalized === \"[::1]\" ||\n normalized === \"0.0.0.0\" ||\n normalized === \"::\" ||\n normalized === \"[::]\"\n );\n};\n\nexport const getAllowedWriteOrigins = ({\n host,\n port,\n}: {\n host: string;\n port: number;\n}): Set<string> => {\n const allowed = new Set<string>([\n `http://${normalizeOriginHost(host)}:${port}`,\n ]);\n if (isLoopbackLikeHost(host)) {\n allowed.add(`http://127.0.0.1:${port}`);\n allowed.add(`http://localhost:${port}`);\n allowed.add(`http://[::1]:${port}`);\n }\n return allowed;\n};\n\nexport const validateWriteRequest = ({\n request,\n host,\n port,\n}: {\n request: IncomingMessage;\n host: string;\n port: number;\n}): RequestSecurityResult => {\n const contentType = getHeaderValue(request.headers[\"content-type\"]);\n if (\n contentType === undefined ||\n !JSON_CONTENT_TYPE_PATTERN.test(contentType)\n ) {\n return {\n ok: false,\n statusCode: 415,\n payload: {\n error: \"UNSUPPORTED_MEDIA_TYPE\",\n message: \"Write routes require 'Content-Type: application/json'.\",\n },\n };\n }\n\n const originHeader = getHeaderValue(request.headers.origin);\n const refererHeader = getHeaderValue(request.headers.referer);\n const secFetchSite = getHeaderValue(request.headers[\"sec-fetch-site\"])\n ?.trim()\n .toLowerCase();\n const allowedOrigins = getAllowedWriteOrigins({ host, port });\n\n if (\n secFetchSite !== undefined &&\n !ALLOWED_SEC_FETCH_SITE_VALUES.has(secFetchSite)\n ) {\n return forbiddenOrigin(\n \"Cross-site browser requests to test-intelligence write routes are blocked.\",\n );\n }\n\n if (originHeader !== undefined) {\n const origin = normalizeOrigin(originHeader);\n if (origin === undefined || !allowedOrigins.has(origin)) {\n return forbiddenOrigin(\n \"Only same-origin browser requests may access test-intelligence write routes.\",\n );\n }\n }\n\n if (refererHeader !== undefined) {\n const refererOrigin = normalizeOrigin(refererHeader);\n if (refererOrigin === undefined || !allowedOrigins.has(refererOrigin)) {\n return forbiddenOrigin(\n \"Only same-origin browser requests may access test-intelligence write routes.\",\n );\n }\n }\n\n return { ok: true };\n};\n\nconst forbiddenOrigin = (message: string): RequestSecurityErr => ({\n ok: false,\n statusCode: 403,\n payload: { error: \"FORBIDDEN_REQUEST_ORIGIN\", message },\n});\n\nconst normalizeConfiguredBearerToken = (\n value: string | undefined,\n): string | undefined => {\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n};\n\nexport const readBearerToken = (\n request: IncomingMessage,\n): string | undefined => {\n const authorization = getHeaderValue(request.headers.authorization);\n if (authorization === undefined) {\n return undefined;\n }\n\n const expectedScheme = \"bearer\";\n if (authorization.length <= expectedScheme.length) {\n return undefined;\n }\n\n for (let index = 0; index < expectedScheme.length; index += 1) {\n const code = authorization.charCodeAt(index);\n const lowered =\n code >= 0x41 && code <= 0x5a\n ? String.fromCharCode(code + 0x20)\n : authorization[index];\n if (lowered !== expectedScheme[index]) {\n return undefined;\n }\n }\n\n let tokenStart = expectedScheme.length;\n while (tokenStart < authorization.length) {\n const code = authorization.charCodeAt(tokenStart);\n if (code !== 0x20 && code !== 0x09) {\n break;\n }\n tokenStart += 1;\n }\n\n if (\n tokenStart === expectedScheme.length ||\n tokenStart >= authorization.length\n ) {\n return undefined;\n }\n\n let tokenEnd = authorization.length;\n while (tokenEnd > tokenStart) {\n const code = authorization.charCodeAt(tokenEnd - 1);\n if (code !== 0x20 && code !== 0x09) {\n break;\n }\n tokenEnd -= 1;\n }\n\n return tokenEnd > tokenStart\n ? authorization.slice(tokenStart, tokenEnd)\n : undefined;\n};\n\nconst tokensMatch = (expected: string, candidate: string): boolean => {\n const expectedDigest = createHash(\"sha256\").update(expected, \"utf8\").digest();\n const candidateDigest = createHash(\"sha256\")\n .update(candidate, \"utf8\")\n .digest();\n return timingSafeEqual(expectedDigest, candidateDigest);\n};\n\nexport const validateBearerToken = ({\n request,\n bearerToken,\n routeLabel,\n}: {\n request: IncomingMessage;\n bearerToken: string | undefined;\n routeLabel: string;\n}): BearerAuthResult => {\n const configuredToken = normalizeConfiguredBearerToken(bearerToken);\n if (configuredToken === undefined) {\n return {\n ok: false,\n statusCode: 503,\n payload: {\n error: \"AUTHENTICATION_UNAVAILABLE\",\n message: `${routeLabel} writes are disabled until server bearer authentication is configured.`,\n },\n };\n }\n\n const receivedToken = readBearerToken(request);\n if (\n receivedToken !== undefined &&\n tokensMatch(configuredToken, receivedToken)\n ) {\n return { ok: true, principal: { scheme: \"bearer\" } };\n }\n\n return {\n ok: false,\n statusCode: 401,\n payload: {\n error: \"UNAUTHORIZED\",\n message: `${routeLabel} writes require a valid Bearer token.`,\n },\n wwwAuthenticate: `Bearer realm=\"${TEST_INTELLIGENCE_BEARER_REALM}\"`,\n };\n};\n","/**\n * Path-decoding and path-safety helpers shared by every route parser.\n *\n * `safeDecode` returns the {@link INVALID_PATH_ENCODING} sentinel rather\n * than throwing on malformed percent-encoding — the caller decides how to\n * respond (the route parser turns this into `400 BAD_REQUEST`).\n *\n * `normalizePlatformPath` canonicalises backslash-separated paths to forward\n * slashes and rejects Windows absolute / UNC forms. These checks run before\n * any filesystem access and are exercised by both the unit and fuzz suites.\n */\n\n/** Sentinel returned by {@link safeDecode} when the input is malformed. */\nexport const INVALID_PATH_ENCODING: unique symbol = Symbol(\n \"INVALID_PATH_ENCODING\",\n);\n\nexport type SafeDecodeResult = string | typeof INVALID_PATH_ENCODING;\n\nexport const safeDecode = (raw: string): SafeDecodeResult => {\n try {\n return decodeURIComponent(raw);\n } catch {\n return INVALID_PATH_ENCODING;\n }\n};\n\nexport interface PlatformPathOk {\n readonly ok: true;\n readonly normalized: string;\n}\n\nexport interface PlatformPathErr {\n readonly ok: false;\n readonly reason: string;\n}\n\nexport type PlatformPathResult = PlatformPathOk | PlatformPathErr;\n\nconst WINDOWS_DRIVE_LETTER_RE = /^[A-Za-z]:[\\\\/]/u;\nconst UNC_PREFIX_RE = /^[\\\\/]{2}/u;\n\nexport const normalizePlatformPath = (input: string): PlatformPathResult => {\n if (WINDOWS_DRIVE_LETTER_RE.test(input)) {\n return { ok: false, reason: \"Windows drive-letter paths are not allowed.\" };\n }\n if (UNC_PREFIX_RE.test(input)) {\n return { ok: false, reason: \"UNC paths are not allowed.\" };\n }\n if (input.startsWith(\"/\")) {\n return { ok: false, reason: \"Absolute paths are not allowed.\" };\n }\n if (input.includes(\"\\0\")) {\n return { ok: false, reason: \"Null bytes in path are not allowed.\" };\n }\n const normalized = input.replaceAll(\"\\\\\", \"/\");\n return { ok: true, normalized };\n};\n\n/**\n * Stable-segment matcher used by every job-, source-, and queue-item-id\n * parser.\n */\nconst STABLE_SEGMENT_RE = /^[A-Za-z0-9_.-]{1,128}$/u;\n\nexport const isSafeIdSegment = (segment: string): boolean => {\n if (!STABLE_SEGMENT_RE.test(segment)) {\n return false;\n }\n // `.` and `..` pass the charset check but are path-traversal sentinels;\n // reject them explicitly so route IDs can be safely joined to file paths.\n if (segment === \".\" || segment === \"..\") {\n return false;\n }\n return true;\n};\n\n/**\n * Trim a single trailing slash from a pathname. Returns the original string\n * when no trailing slash is present. Used by the route parser so\n * `/api/v1/jobs/` and `/api/v1/jobs` route identically.\n */\nexport const stripTrailingSlash = (pathname: string): string => {\n if (pathname.length > 1 && pathname.endsWith(\"/\")) {\n return pathname.slice(0, -1);\n }\n return pathname;\n};\n","/**\n * Standalone Test Intelligence route parser.\n *\n * The parser maps `(method, pathname)` to a discriminated `Route` union\n * so the request handler dispatches by switch rather than chained `if`s.\n * No path component is interpreted before {@link isSafeIdSegment} accepts\n * it — the parser is the only place ID safety is enforced.\n *\n * Routes:\n * GET /healthz\n * GET /readyz\n * GET /openapi.json\n * POST /api/v1/jobs (submit a run)\n * GET /api/v1/jobs/{jobId} (status)\n * GET /api/v1/jobs/{jobId}/events (SSE)\n * POST /api/v1/jobs/{jobId}/evidence/verify\n * POST /api/v1/jobs/{jobId}/audit-dossier/verify\n * POST /api/v1/jobs/{jobId}/provenance/verify\n * POST /api/v1/jobs/{jobId}/seal/verify\n * GET /api/v1/review/{jobId} (list / snapshot)\n * POST /api/v1/review/{jobId}/decision (write action)\n * POST /api/v1/tms/push\n * POST /api/v1/execution/pull\n * POST /api/v1/onboard\n * POST /api/v1/figma-export\n * OPTIONS * (CORS preflight)\n */\n\nimport { API_ROUTE_PREFIX } from \"./constants.js\";\nimport { isSafeIdSegment, stripTrailingSlash } from \"./route-params.js\";\n\nexport type Route =\n | { readonly kind: \"healthz\" }\n | { readonly kind: \"readyz\" }\n | { readonly kind: \"openapi\" }\n | { readonly kind: \"submit_job\" }\n | { readonly kind: \"job_status\"; readonly jobId: string }\n | { readonly kind: \"job_events\"; readonly jobId: string }\n | { readonly kind: \"verify_evidence\"; readonly jobId: string }\n | { readonly kind: \"verify_audit_dossier\"; readonly jobId: string }\n | { readonly kind: \"verify_provenance\"; readonly jobId: string }\n | { readonly kind: \"verify_seal\"; readonly jobId: string }\n | { readonly kind: \"review_snapshot\"; readonly jobId: string }\n | { readonly kind: \"review_decision\"; readonly jobId: string }\n | { readonly kind: \"tms_push\" }\n | { readonly kind: \"execution_pull\" }\n | { readonly kind: \"onboard\" }\n | { readonly kind: \"figma_export\" }\n | { readonly kind: \"cors_preflight\" };\n\nexport type RouteParseFailureReason =\n | \"unknown_route\"\n | \"method_not_allowed\"\n | \"unsafe_id_segment\"\n | \"empty_segment\";\n\nexport type RouteParseResult =\n | { readonly ok: true; readonly route: Route }\n | {\n readonly ok: false;\n readonly reason: RouteParseFailureReason;\n readonly allowedMethods?: readonly string[];\n };\n\nexport const parseTestIntelligenceRoute = ({\n method,\n pathname,\n}: {\n method: string;\n pathname: string;\n}): RouteParseResult => {\n const upper = method.toUpperCase();\n if (upper === \"OPTIONS\") {\n return { ok: true, route: { kind: \"cors_preflight\" } };\n }\n\n const normalized = stripTrailingSlash(pathname);\n\n if (normalized === \"/healthz\") {\n return methodGuard(upper, \"GET\", { kind: \"healthz\" });\n }\n if (normalized === \"/readyz\") {\n return methodGuard(upper, \"GET\", { kind: \"readyz\" });\n }\n if (normalized === \"/openapi.json\") {\n return methodGuard(upper, \"GET\", { kind: \"openapi\" });\n }\n\n if (!normalized.startsWith(`${API_ROUTE_PREFIX}/`)) {\n return { ok: false, reason: \"unknown_route\" };\n }\n const rest = normalized.slice(API_ROUTE_PREFIX.length + 1);\n const segments = rest.split(\"/\");\n return dispatchSegments(upper, segments);\n};\n\nconst methodGuard = (\n method: string,\n allowed: string,\n route: Route,\n): RouteParseResult => {\n if (method !== allowed) {\n return {\n ok: false,\n reason: \"method_not_allowed\",\n allowedMethods: [allowed],\n };\n }\n return { ok: true, route };\n};\n\nconst dispatchSegments = (\n method: string,\n segments: readonly string[],\n): RouteParseResult => {\n if (segments.length === 0 || segments[0] === \"\") {\n return { ok: false, reason: \"unknown_route\" };\n }\n const head = segments[0]!;\n const tail = segments.slice(1);\n switch (head) {\n case \"jobs\":\n return dispatchJobs(method, tail);\n case \"review\":\n return dispatchReview(method, tail);\n case \"tms\":\n return dispatchTms(method, tail);\n case \"execution\":\n return dispatchExecution(method, tail);\n case \"onboard\":\n return dispatchSingleton(method, tail, \"POST\", { kind: \"onboard\" });\n case \"figma-export\":\n return dispatchSingleton(method, tail, \"POST\", { kind: \"figma_export\" });\n default:\n return { ok: false, reason: \"unknown_route\" };\n }\n};\n\nconst dispatchJobs = (\n method: string,\n tail: readonly string[],\n): RouteParseResult => {\n if (tail.length === 0) {\n return methodGuard(method, \"POST\", { kind: \"submit_job\" });\n }\n const jobId = tail[0]!;\n if (jobId === \"\") {\n return { ok: false, reason: \"empty_segment\" };\n }\n if (!isSafeIdSegment(jobId)) {\n return { ok: false, reason: \"unsafe_id_segment\" };\n }\n if (tail.length === 1) {\n return methodGuard(method, \"GET\", { kind: \"job_status\", jobId });\n }\n if (tail.length === 2 && tail[1] === \"events\") {\n return methodGuard(method, \"GET\", { kind: \"job_events\", jobId });\n }\n if (tail.length === 3 && tail[2] === \"verify\") {\n return jobsVerifySubroute(method, jobId, tail[1]!);\n }\n return { ok: false, reason: \"unknown_route\" };\n};\n\nconst jobsVerifySubroute = (\n method: string,\n jobId: string,\n subject: string,\n): RouteParseResult => {\n switch (subject) {\n case \"evidence\":\n return methodGuard(method, \"POST\", { kind: \"verify_evidence\", jobId });\n case \"audit-dossier\":\n return methodGuard(method, \"POST\", {\n kind: \"verify_audit_dossier\",\n jobId,\n });\n case \"provenance\":\n return methodGuard(method, \"POST\", { kind: \"verify_provenance\", jobId });\n case \"seal\":\n return methodGuard(method, \"POST\", { kind: \"verify_seal\", jobId });\n default:\n return { ok: false, reason: \"unknown_route\" };\n }\n};\n\nconst dispatchReview = (\n method: string,\n tail: readonly string[],\n): RouteParseResult => {\n if (tail.length === 0) {\n return { ok: false, reason: \"unknown_route\" };\n }\n const jobId = tail[0]!;\n if (jobId === \"\") {\n return { ok: false, reason: \"empty_segment\" };\n }\n if (!isSafeIdSegment(jobId)) {\n return { ok: false, reason: \"unsafe_id_segment\" };\n }\n if (tail.length === 1) {\n return methodGuard(method, \"GET\", { kind: \"review_snapshot\", jobId });\n }\n if (tail.length === 2 && tail[1] === \"decision\") {\n return methodGuard(method, \"POST\", { kind: \"review_decision\", jobId });\n }\n return { ok: false, reason: \"unknown_route\" };\n};\n\nconst dispatchTms = (\n method: string,\n tail: readonly string[],\n): RouteParseResult => {\n return dispatchSingleton(method, tail, \"POST\", { kind: \"tms_push\" }, [\n \"push\",\n ]);\n};\n\nconst dispatchExecution = (\n method: string,\n tail: readonly string[],\n): RouteParseResult => {\n return dispatchSingleton(method, tail, \"POST\", { kind: \"execution_pull\" }, [\n \"pull\",\n ]);\n};\n\nconst dispatchSingleton = (\n method: string,\n tail: readonly string[],\n allowedMethod: string,\n route: Route,\n expectedTail: readonly string[] = [],\n): RouteParseResult => {\n if (tail.length !== expectedTail.length) {\n return { ok: false, reason: \"unknown_route\" };\n }\n for (let i = 0; i < expectedTail.length; i += 1) {\n if (tail[i] !== expectedTail[i]) {\n return { ok: false, reason: \"unknown_route\" };\n }\n }\n return methodGuard(method, allowedMethod, route);\n};\n","/**\n * Default per-route handler implementations.\n *\n * Every handler in the default table either answers from in-process state\n * (`healthz`, `readyz`, `openapi`, `cors_preflight`) or returns\n * `503 LLM_GATEWAY_UNCONFIGURED` so the route is reachable but the\n * operator must explicitly wire it via\n * {@link TestIntelligenceRequestHandlerOptions.routeHandlers}. This split\n * keeps every handler small and lets the integration tests substitute a\n * mock for any single route without rebuilding the dispatcher.\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { MAX_REQUEST_BODY_BYTES, MAX_SUBMIT_BODY_BYTES } from \"./constants.js\";\nimport {\n beginSseResponse,\n readJsonBody,\n writeErrorResponse,\n writeJsonResponse,\n} from \"./http-helpers.js\";\nimport type { Route } from \"./test-intelligence-routes.js\";\nimport type {\n RouteHandler,\n RouteHandlerContext,\n TestIntelligenceRequestHandlerOptions,\n} from \"./request-handler-types.js\";\n\nexport const buildHandlerTable = (\n options: TestIntelligenceRequestHandlerOptions,\n): Record<Route[\"kind\"], RouteHandler> => {\n const defaults: Record<Route[\"kind\"], RouteHandler> = {\n healthz: handleHealthz,\n readyz: ({ response }) => handleReadyz(response, options),\n openapi: handleOpenapi,\n cors_preflight: ({ response }) => handleCorsPreflight(response, options),\n submit_job: handleNotImplemented(\"submit_job\"),\n job_status: handleNotImplemented(\"job_status\"),\n job_events: handleSseStub,\n verify_evidence: handleNotImplemented(\"verify_evidence\"),\n verify_audit_dossier: handleNotImplemented(\"verify_audit_dossier\"),\n verify_provenance: handleNotImplemented(\"verify_provenance\"),\n verify_seal: handleNotImplemented(\"verify_seal\"),\n review_snapshot: handleNotImplemented(\"review_snapshot\"),\n review_decision: handleNotImplemented(\"review_decision\"),\n tms_push: handleNotImplemented(\"tms_push\"),\n execution_pull: handleNotImplemented(\"execution_pull\"),\n onboard: handleNotImplemented(\"onboard\"),\n figma_export: handleNotImplemented(\"figma_export\"),\n };\n const overrides = options.routeHandlers ?? {};\n for (const [kind, handler] of Object.entries(overrides) as ReadonlyArray<\n [Route[\"kind\"], RouteHandler]\n >) {\n defaults[kind] = handler;\n }\n return defaults;\n};\n\nconst handleHealthz: RouteHandler = ({ response }) => {\n writeJsonResponse({\n response,\n statusCode: 200,\n payload: { status: \"ok\", checkedAt: new Date().toISOString() },\n });\n};\n\nconst handleReadyz = (\n response: ServerResponse,\n options: TestIntelligenceRequestHandlerOptions,\n): void => {\n writeJsonResponse({\n response,\n statusCode: 200,\n payload: {\n status: \"ready\",\n featureGate: options.testIntelligenceEnabled ? \"enabled\" : \"disabled\",\n authConfigured: options.bearerToken !== undefined,\n checkedAt: new Date().toISOString(),\n },\n });\n};\n\nconst handleOpenapi: RouteHandler = ({ response }) => {\n // The server factory overrides this handler with one that serves the\n // checked-in document from packages/server/src/openapi.ts. The default\n // stub keeps the route addressable when the handler is mounted in isolation.\n writeJsonResponse({\n response,\n statusCode: 200,\n payload: {\n openapi: \"3.1.0\",\n info: { title: \"Test Intelligence API\", version: \"0.0.1\" },\n paths: {},\n },\n });\n};\n\nconst handleCorsPreflight = (\n response: ServerResponse,\n options: TestIntelligenceRequestHandlerOptions,\n): void => {\n const allowedOrigin = options.allowedCorsOrigins?.[0];\n if (allowedOrigin !== undefined) {\n response.setHeader(\"Access-Control-Allow-Origin\", allowedOrigin);\n }\n response.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n response.setHeader(\n \"Access-Control-Allow-Headers\",\n \"Content-Type, Authorization\",\n );\n response.setHeader(\"Access-Control-Max-Age\", \"600\");\n response.statusCode = 204;\n response.end();\n};\n\nconst handleNotImplemented = (routeKind: string): RouteHandler => {\n return ({\n request,\n response,\n route,\n audit,\n requestId,\n }: RouteHandlerContext) => {\n void readJsonBodyForRoute(request, route).then((body) => {\n audit.record({\n action: `${routeKind}.received`,\n subject: routeKind,\n outcome: body.ok ? \"ok\" : \"denied\",\n requestId,\n });\n writeErrorResponse({\n response,\n code: \"LLM_GATEWAY_UNCONFIGURED\",\n message: `Route '${routeKind}' is reachable but no operator-supplied handler is wired.`,\n });\n });\n };\n};\n\nconst handleSseStub: RouteHandler = ({ response }) => {\n const sse = beginSseResponse(response);\n sse.writeRetry(15_000);\n sse.writeEvent({\n event: \"noop\",\n data: { message: \"No event source is wired for this job.\" },\n id: \"0\",\n });\n sse.end();\n};\n\nconst readJsonBodyForRoute = async (\n request: IncomingMessage,\n route: Route,\n): Promise<{ ok: boolean }> => {\n const maxBytes =\n route.kind === \"submit_job\"\n ? MAX_SUBMIT_BODY_BYTES\n : MAX_REQUEST_BODY_BYTES;\n const result = await readJsonBody({ request, maxBytes });\n return { ok: result.ok };\n};\n","/**\n * Standalone Test Intelligence HTTP request dispatcher.\n *\n * `createTestIntelligenceRequestHandler` returns a Node `IncomingMessage` /\n * `ServerResponse` callback that:\n * 1. Parses the route via {@link parseTestIntelligenceRoute}.\n * 2. Enforces per-IP+route rate limiting.\n * 3. Applies the feature gate, same-origin, and bearer checks on write\n * routes (fail-closed).\n * 4. Dispatches to a per-route handler from {@link buildHandlerTable}.\n *\n * Business logic lives in the route handlers (`route-handlers.ts`); the\n * dispatcher here only translates HTTP envelopes to function calls.\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { WorkspaceRuntimeLogger } from \"@oscharko-dev/ti-security\";\nimport { DEFAULT_RATE_LIMIT_PER_MINUTE } from \"./constants.js\";\nimport type { TestIntelligenceErrorCode } from \"./error-codes.js\";\nimport { resolveClientKey, writeErrorResponse } from \"./http-helpers.js\";\nimport {\n createAuditLogger,\n createRequestLogger,\n type AuditLogger,\n type RequestLogger,\n} from \"./observability.js\";\nimport { createRateLimiter, type RateLimiter } from \"./rate-limit.js\";\nimport {\n validateBearerToken,\n validateWriteRequest,\n} from \"./request-security.js\";\nimport {\n parseTestIntelligenceRoute,\n type Route,\n type RouteParseFailureReason,\n} from \"./test-intelligence-routes.js\";\nimport { buildHandlerTable } from \"./route-handlers.js\";\nimport type {\n RouteHandler,\n TestIntelligenceRequestHandler,\n TestIntelligenceRequestHandlerOptions,\n} from \"./request-handler-types.js\";\n\nexport type {\n RouteHandler,\n RouteHandlerContext,\n TestIntelligenceRequestHandler,\n TestIntelligenceRequestHandlerOptions,\n} from \"./request-handler-types.js\";\n\nexport const createTestIntelligenceRequestHandler = (\n options: TestIntelligenceRequestHandlerOptions,\n): TestIntelligenceRequestHandler => {\n const rateLimiter = createRateLimiter({\n requestsPerMinute:\n options.requestsPerMinute ?? DEFAULT_RATE_LIMIT_PER_MINUTE,\n });\n const requestLog = createRequestLogger(options.logger);\n const audit = createAuditLogger({\n write: options.auditWrite ?? defaultAuditWrite(options.logger),\n });\n const handlers = buildHandlerTable(options);\n const nowMs = options.nowMs ?? (() => Date.now());\n\n return (request, response) => {\n void runHandler({\n request,\n response,\n options,\n rateLimiter,\n requestLog,\n audit,\n handlers,\n nowMs,\n });\n };\n};\n\ninterface RunHandlerInput {\n request: IncomingMessage;\n response: ServerResponse;\n options: TestIntelligenceRequestHandlerOptions;\n rateLimiter: RateLimiter;\n requestLog: RequestLogger;\n audit: AuditLogger;\n handlers: Record<Route[\"kind\"], RouteHandler>;\n nowMs: () => number;\n}\n\nconst runHandler = async (input: RunHandlerInput): Promise<void> => {\n const { request, response, options, requestLog } = input;\n const start = Date.now();\n const requestId = requestLog.newRequestId();\n const clientKey = resolveClientKey(request);\n const method = request.method ?? \"GET\";\n const pathname = extractPathname(request.url ?? \"/\");\n\n try {\n const dispatched = await dispatch({\n ...input,\n method,\n pathname,\n requestId,\n clientKey,\n });\n requestLog.log({\n requestId,\n method,\n path: pathname,\n statusCode: response.statusCode,\n durationMs: Date.now() - start,\n clientKey,\n route: dispatched,\n });\n } catch (error) {\n handleUnexpectedError({ response, error, options, requestId });\n requestLog.log({\n requestId,\n method,\n path: pathname,\n statusCode: response.statusCode,\n durationMs: Date.now() - start,\n clientKey,\n });\n }\n};\n\ninterface DispatchInput extends RunHandlerInput {\n method: string;\n pathname: string;\n requestId: string;\n clientKey: string;\n}\n\nconst dispatch = async (input: DispatchInput): Promise<string> => {\n const {\n request,\n response,\n method,\n pathname,\n options,\n rateLimiter,\n handlers,\n requestId,\n clientKey,\n audit,\n nowMs,\n } = input;\n\n const parse = parseTestIntelligenceRoute({ method, pathname });\n if (!parse.ok) {\n emitParseFailure({\n response,\n reason: parse.reason,\n allowed: parse.allowedMethods,\n });\n return `parse:${parse.reason}`;\n }\n\n const route = parse.route;\n const routeKey = `${method.toUpperCase()} ${route.kind}`;\n const limit = rateLimiter.check({\n clientKey,\n routeKey,\n nowMs: nowMs(),\n });\n if (!limit.ok) {\n writeErrorResponse({\n response,\n code: \"RATE_LIMITED\",\n message: \"Too many requests; retry after the indicated interval.\",\n extraHeaders: { \"Retry-After\": String(limit.retryAfterSeconds) },\n });\n return route.kind;\n }\n\n if (requiresWriteGate(route)) {\n const gate = enforceWriteGate({ request, response, options, route });\n if (!gate.ok) {\n return route.kind;\n }\n }\n\n await handlers[route.kind]({\n request,\n response,\n route,\n requestId,\n clientKey,\n audit,\n logger: options.logger,\n });\n return route.kind;\n};\n\nconst requiresWriteGate = (route: Route): boolean => {\n switch (route.kind) {\n case \"healthz\":\n case \"readyz\":\n case \"openapi\":\n case \"cors_preflight\":\n case \"job_status\":\n case \"job_events\":\n case \"review_snapshot\":\n return false;\n default:\n return true;\n }\n};\n\nconst enforceWriteGate = ({\n request,\n response,\n options,\n route,\n}: {\n request: IncomingMessage;\n response: ServerResponse;\n options: TestIntelligenceRequestHandlerOptions;\n route: Route;\n}): { ok: boolean } => {\n if (!options.testIntelligenceEnabled) {\n writeErrorResponse({\n response,\n code: \"FEATURE_GATE_DISABLED\",\n message: \"The test-intelligence feature gate is disabled on this server.\",\n });\n return { ok: false };\n }\n const writeCheck = validateWriteRequest({\n request,\n host: options.host,\n port: options.port,\n });\n if (!writeCheck.ok) {\n writeErrorResponse({\n response,\n code: writeCheck.payload.error,\n message: writeCheck.payload.message,\n statusCode: writeCheck.statusCode,\n });\n return { ok: false };\n }\n const auth = validateBearerToken({\n request,\n bearerToken: options.bearerToken,\n routeLabel: route.kind,\n });\n if (!auth.ok) {\n writeErrorResponse({\n response,\n code: auth.payload.error,\n message: auth.payload.message,\n statusCode: auth.statusCode,\n ...(auth.wwwAuthenticate !== undefined\n ? { extraHeaders: { \"WWW-Authenticate\": auth.wwwAuthenticate } }\n : {}),\n });\n return { ok: false };\n }\n return { ok: true };\n};\n\nconst emitParseFailure = ({\n response,\n reason,\n allowed,\n}: {\n response: ServerResponse;\n reason: RouteParseFailureReason;\n allowed: readonly string[] | undefined;\n}): void => {\n if (reason === \"method_not_allowed\") {\n writeErrorResponse({\n response,\n code: \"METHOD_NOT_ALLOWED\",\n message: \"The requested method is not allowed on this route.\",\n ...(allowed !== undefined\n ? { extraHeaders: { Allow: allowed.join(\", \") } }\n : {}),\n });\n return;\n }\n const code: TestIntelligenceErrorCode =\n reason === \"unsafe_id_segment\" || reason === \"empty_segment\"\n ? \"BAD_REQUEST\"\n : \"NOT_FOUND\";\n const message =\n code === \"BAD_REQUEST\"\n ? \"The route contains an unsafe or empty path segment.\"\n : \"The requested route does not exist.\";\n writeErrorResponse({ response, code, message });\n};\n\nconst extractPathname = (url: string): string => {\n const queryIndex = url.indexOf(\"?\");\n return queryIndex === -1 ? url : url.slice(0, queryIndex);\n};\n\nconst defaultAuditWrite =\n (logger: WorkspaceRuntimeLogger) =>\n (line: string): void => {\n logger.log({ level: \"info\", message: `audit ${line.trimEnd()}` });\n };\n\nconst handleUnexpectedError = ({\n response,\n error,\n options,\n requestId,\n}: {\n response: ServerResponse;\n error: unknown;\n options: TestIntelligenceRequestHandlerOptions;\n requestId: string;\n}): void => {\n const message = error instanceof Error ? error.message : \"Unknown error.\";\n options.logger.log({\n level: \"error\",\n message: `request handler failure: ${message}`,\n requestId,\n });\n if (!response.headersSent) {\n writeErrorResponse({\n response,\n code: \"INTERNAL_ERROR\",\n message: \"An unexpected error occurred handling the request.\",\n });\n } else if (!response.writableEnded) {\n response.end();\n }\n};\n","/**\n * Package-identity constants and helpers describing the published\n * `@oscharko-dev/test-intelligence` build at runtime.\n *\n * Lives in its own file (not `index.ts`) so other modules in this package\n * can import the version constant without forming an `index.ts` import\n * cycle through the server factory barrel.\n */\n\n/** Release maturity of a published build, derived from its semantic version. */\nexport type ReleaseStage = \"pre-beta\" | \"beta\" | \"stable\";\n\n/**\n * Immutable identity and release metadata describing the running package.\n */\nexport interface PackageIdentity {\n readonly name: string;\n readonly version: string;\n readonly stage: ReleaseStage;\n}\n\nexport const PACKAGE_NAME: string = \"@oscharko-dev/test-intelligence\";\n\nexport const PACKAGE_VERSION: string = \"0.0.1-beta.0\";\n\nexport function resolveReleaseStage(version: string): ReleaseStage {\n const separatorIndex = version.indexOf(\"-\");\n if (separatorIndex === -1) {\n return \"stable\";\n }\n const preRelease = version.slice(separatorIndex + 1);\n return preRelease.startsWith(\"beta\") ? \"beta\" : \"pre-beta\";\n}\n\nexport function getPackageIdentity(): PackageIdentity {\n return {\n name: PACKAGE_NAME,\n version: PACKAGE_VERSION,\n stage: resolveReleaseStage(PACKAGE_VERSION),\n };\n}\n","/**\n * OpenAPI 3.1 document for the standalone Test Intelligence HTTP surface.\n *\n * The document is the source of truth: the drift-guard test\n * (`openapi.test.ts`) asserts that every route surfaced by\n * {@link parseTestIntelligenceRoute} appears in `paths` here.\n */\n\nimport { API_ROUTE_PREFIX } from \"./constants.js\";\nimport { PACKAGE_VERSION } from \"./package-identity.js\";\n\nexport interface OpenApiDocument {\n readonly openapi: \"3.1.0\";\n readonly info: {\n readonly title: string;\n readonly version: string;\n readonly description: string;\n };\n readonly servers: ReadonlyArray<{ readonly url: string }>;\n readonly components: {\n readonly securitySchemes: Readonly<Record<string, unknown>>;\n readonly schemas: Readonly<Record<string, unknown>>;\n };\n readonly paths: Readonly<Record<string, unknown>>;\n}\n\nconst ERROR_ENVELOPE_SCHEMA: Readonly<Record<string, unknown>> = {\n type: \"object\",\n required: [\"error\", \"message\"],\n properties: {\n error: { type: \"string\" },\n message: { type: \"string\" },\n },\n additionalProperties: false,\n};\n\nconst READYZ_SCHEMA: Readonly<Record<string, unknown>> = {\n type: \"object\",\n required: [\"status\", \"featureGate\", \"authConfigured\", \"checkedAt\"],\n properties: {\n status: { type: \"string\", enum: [\"ready\"] },\n featureGate: { type: \"string\", enum: [\"enabled\", \"disabled\"] },\n authConfigured: { type: \"boolean\" },\n checkedAt: { type: \"string\", format: \"date-time\" },\n },\n additionalProperties: false,\n};\n\nconst HEALTHZ_SCHEMA: Readonly<Record<string, unknown>> = {\n type: \"object\",\n required: [\"status\", \"checkedAt\"],\n properties: {\n status: { type: \"string\", enum: [\"ok\"] },\n checkedAt: { type: \"string\", format: \"date-time\" },\n },\n additionalProperties: false,\n};\n\nconst errorResponse = (\n description: string,\n): Readonly<Record<string, unknown>> => ({\n description,\n content: {\n \"application/json\": {\n schema: { $ref: \"#/components/schemas/Error\" },\n },\n },\n});\n\nconst writeRoute = (\n summary: string,\n operationId: string,\n): Readonly<Record<string, unknown>> => ({\n post: {\n summary,\n operationId,\n security: [{ bearerAuth: [] }],\n requestBody: {\n required: true,\n content: { \"application/json\": { schema: { type: \"object\" } } },\n },\n responses: {\n \"200\": { description: \"Operation completed.\" },\n \"401\": errorResponse(\"Bearer token is missing or incorrect.\"),\n \"403\": errorResponse(\"Feature gate or origin policy denied the call.\"),\n \"413\": errorResponse(\"Request body exceeded the configured limit.\"),\n \"415\": errorResponse(\"Content-Type is not application/json.\"),\n \"429\": errorResponse(\"Per-client rate limit was exceeded.\"),\n \"503\": errorResponse(\"Server is not configured for this route.\"),\n },\n },\n});\n\nconst readRoute = (\n summary: string,\n operationId: string,\n responseSchemaRef: string,\n): Readonly<Record<string, unknown>> => ({\n get: {\n summary,\n operationId,\n responses: {\n \"200\": {\n description: \"Result.\",\n content: {\n \"application/json\": { schema: { $ref: responseSchemaRef } },\n },\n },\n \"404\": errorResponse(\"Resource not found.\"),\n \"429\": errorResponse(\"Per-client rate limit was exceeded.\"),\n },\n },\n});\n\nconst jobScopedWriteRoute = (\n summary: string,\n operationId: string,\n): Readonly<Record<string, unknown>> => {\n const route = writeRoute(summary, operationId) as {\n post: Record<string, unknown>;\n };\n route.post[\"parameters\"] = [\n {\n name: \"jobId\",\n in: \"path\",\n required: true,\n schema: { type: \"string\", pattern: \"^[A-Za-z0-9_.-]{1,128}$\" },\n },\n ];\n return route;\n};\n\nconst buildPaths = (): Readonly<Record<string, unknown>> => ({\n \"/healthz\": readRoute(\n \"Liveness probe.\",\n \"getHealthz\",\n \"#/components/schemas/Healthz\",\n ),\n \"/readyz\": readRoute(\n \"Readiness probe.\",\n \"getReadyz\",\n \"#/components/schemas/Readyz\",\n ),\n \"/openapi.json\": readRoute(\n \"Return this OpenAPI document.\",\n \"getOpenapi\",\n \"#/components/schemas/Error\",\n ),\n [`${API_ROUTE_PREFIX}/jobs`]: writeRoute(\n \"Submit a Test Intelligence run.\",\n \"submitJob\",\n ),\n [`${API_ROUTE_PREFIX}/jobs/{jobId}`]: readRoute(\n \"Read a job's status.\",\n \"getJobStatus\",\n \"#/components/schemas/Error\",\n ),\n [`${API_ROUTE_PREFIX}/jobs/{jobId}/events`]: readRoute(\n \"Server-Sent Events stream of a job's phase events.\",\n \"streamJobEvents\",\n \"#/components/schemas/Error\",\n ),\n [`${API_ROUTE_PREFIX}/jobs/{jobId}/evidence/verify`]: jobScopedWriteRoute(\n \"Verify a job's evidence manifest.\",\n \"verifyJobEvidence\",\n ),\n [`${API_ROUTE_PREFIX}/jobs/{jobId}/audit-dossier/verify`]:\n jobScopedWriteRoute(\n \"Verify a job's audit dossier bundle.\",\n \"verifyJobAuditDossier\",\n ),\n [`${API_ROUTE_PREFIX}/jobs/{jobId}/provenance/verify`]: jobScopedWriteRoute(\n \"Verify a job's provenance document.\",\n \"verifyJobProvenance\",\n ),\n [`${API_ROUTE_PREFIX}/jobs/{jobId}/seal/verify`]: jobScopedWriteRoute(\n \"Verify a job's seal bundle.\",\n \"verifyJobSeal\",\n ),\n [`${API_ROUTE_PREFIX}/review/{jobId}`]: readRoute(\n \"Read the review snapshot for a job.\",\n \"getReviewSnapshot\",\n \"#/components/schemas/Error\",\n ),\n [`${API_ROUTE_PREFIX}/review/{jobId}/decision`]: jobScopedWriteRoute(\n \"Record a review decision.\",\n \"recordReviewDecision\",\n ),\n [`${API_ROUTE_PREFIX}/tms/push`]: writeRoute(\n \"Push completed test cases to a configured TMS adapter.\",\n \"pushToTms\",\n ),\n [`${API_ROUTE_PREFIX}/execution/pull`]: writeRoute(\n \"Pull execution evidence from a TMS adapter.\",\n \"pullExecutionEvidence\",\n ),\n [`${API_ROUTE_PREFIX}/onboard`]: writeRoute(\n \"Run tenant onboarding.\",\n \"runTenantOnboarding\",\n ),\n [`${API_ROUTE_PREFIX}/figma-export`]: writeRoute(\n \"Fetch a Figma file as a TI ingest payload.\",\n \"exportFigmaForTestIntelligence\",\n ),\n});\n\nexport const buildOpenApiDocument = (): OpenApiDocument => ({\n openapi: \"3.1.0\",\n info: {\n title: \"Test Intelligence API\",\n version: PACKAGE_VERSION,\n description:\n \"Standalone HTTP surface for the Test Intelligence runtime. \" +\n \"Every write route is bearer-protected and fails closed when the \" +\n \"test-intelligence feature gate is disabled.\",\n },\n servers: [{ url: \"http://127.0.0.1:1983\" }],\n components: {\n securitySchemes: {\n bearerAuth: {\n type: \"http\",\n scheme: \"bearer\",\n },\n },\n schemas: {\n Error: ERROR_ENVELOPE_SCHEMA,\n Readyz: READYZ_SCHEMA,\n Healthz: HEALTHZ_SCHEMA,\n },\n },\n paths: buildPaths(),\n});\n","/**\n * Standalone Test Intelligence HTTP server factory.\n *\n * `createTestIntelligenceServer` constructs a Node `http.Server` wired to\n * the request dispatcher and returns a small lifecycle object the operator\n * can use to read the bound address, observe the startup timestamp, and\n * shut down gracefully.\n *\n * This module exposes only the standalone Test Intelligence HTTP surface\n * defined in\n * `packages/server/src/test-intelligence-routes.ts`.\n */\n\nimport { createServer, type Server } from \"node:http\";\nimport {\n DEFAULT_HOST,\n DEFAULT_PORT,\n DEFAULT_RATE_LIMIT_PER_MINUTE,\n resolveTestIntelligenceEnabled,\n} from \"./constants.js\";\nimport { createTestIntelligenceRequestHandler } from \"./request-handler.js\";\nimport type {\n RouteHandler,\n TestIntelligenceRequestHandlerOptions,\n} from \"./request-handler-types.js\";\nimport { buildOpenApiDocument } from \"./openapi.js\";\nimport { writeJsonResponse } from \"./http-helpers.js\";\nimport type { WorkspaceRuntimeLogger } from \"@oscharko-dev/ti-security\";\n\nexport interface CreateTestIntelligenceServerOptions {\n /** Bind host (defaults to {@link DEFAULT_HOST} — loopback). */\n readonly host?: string;\n /** Bind port (defaults to {@link DEFAULT_PORT}; pass 0 for ephemeral). */\n readonly port?: number;\n /**\n * Configured operator bearer token. Without it every write route returns\n * `503 AUTHENTICATION_UNAVAILABLE`.\n */\n readonly bearerToken?: string;\n /**\n * Operator-provided structured logger. The factory does NOT create a\n * default file sink — leaving logging to the operator is part of the\n * zero-telemetry posture.\n */\n readonly logger: WorkspaceRuntimeLogger;\n /** Optional audit-log line writer. Defaults to `logger.log({level:\"info\"})`. */\n readonly auditWrite?: (line: string) => void;\n /** Optional per-route handler overrides for tests / advanced wiring. */\n readonly routeHandlers?: TestIntelligenceRequestHandlerOptions[\"routeHandlers\"];\n /** Rate-limit budget per client per minute. */\n readonly requestsPerMinute?: number;\n /** Allowed CORS origins for preflight responses. */\n readonly allowedCorsOrigins?: readonly string[];\n /**\n * Resolved test-intelligence feature gate. Defaults to\n * {@link resolveTestIntelligenceEnabled}. Pass `false` to force the\n * server up in a read-only posture.\n */\n readonly testIntelligenceEnabled?: boolean;\n /** Process env (test seam). */\n readonly env?: NodeJS.ProcessEnv;\n}\n\nexport interface TestIntelligenceServer {\n readonly server: Server;\n readonly host: string;\n readonly port: number;\n readonly url: string;\n readonly startedAt: number;\n readonly close: () => Promise<void>;\n}\n\nexport const createTestIntelligenceServer = async (\n options: CreateTestIntelligenceServerOptions,\n): Promise<TestIntelligenceServer> => {\n const host = options.host ?? DEFAULT_HOST;\n const requestedPort = options.port ?? DEFAULT_PORT;\n const testIntelligenceEnabled =\n options.testIntelligenceEnabled ??\n resolveTestIntelligenceEnabled(options.env);\n\n const openApiHandler: RouteHandler = ({ response }) => {\n writeJsonResponse({\n response,\n statusCode: 200,\n payload: buildOpenApiDocument(),\n });\n };\n\n const mergedRouteHandlers: TestIntelligenceRequestHandlerOptions[\"routeHandlers\"] =\n {\n openapi: openApiHandler,\n ...(options.routeHandlers ?? {}),\n };\n\n const handler = createTestIntelligenceRequestHandler({\n host,\n port: requestedPort,\n logger: options.logger,\n testIntelligenceEnabled,\n requestsPerMinute:\n options.requestsPerMinute ?? DEFAULT_RATE_LIMIT_PER_MINUTE,\n routeHandlers: mergedRouteHandlers,\n ...(options.bearerToken !== undefined\n ? { bearerToken: options.bearerToken }\n : {}),\n ...(options.auditWrite !== undefined\n ? { auditWrite: options.auditWrite }\n : {}),\n ...(options.allowedCorsOrigins !== undefined\n ? { allowedCorsOrigins: options.allowedCorsOrigins }\n : {}),\n });\n\n const server = createServer((request, response) => {\n handler(request, response);\n });\n\n await listen(server, host, requestedPort);\n const address = server.address();\n const boundPort =\n address !== null && typeof address === \"object\"\n ? address.port\n : requestedPort;\n const url = `http://${formatHost(host)}:${String(boundPort)}`;\n const startedAt = Date.now();\n\n return {\n server,\n host,\n port: boundPort,\n url,\n startedAt,\n close: () => closeServer(server),\n };\n};\n\nconst listen = (server: Server, host: string, port: number): Promise<void> => {\n return new Promise((resolve, reject) => {\n const onError = (error: NodeJS.ErrnoException): void => {\n server.off(\"listening\", onListening);\n reject(error);\n };\n const onListening = (): void => {\n server.off(\"error\", onError);\n resolve();\n };\n server.once(\"error\", onError);\n server.once(\"listening\", onListening);\n server.listen(port, host);\n });\n};\n\nconst closeServer = (server: Server): Promise<void> => {\n return new Promise((resolve, reject) => {\n server.close((error) => {\n if (error !== undefined) {\n reject(error);\n return;\n }\n resolve();\n });\n });\n};\n\nconst formatHost = (host: string): string => {\n if (host.includes(\":\") && !host.startsWith(\"[\")) {\n return `[${host}]`;\n }\n return host;\n};\n\nexport type { RouteHandler } from \"./request-handler-types.js\";\n","/**\n * Container ENTRYPOINT for the standalone Test Intelligence runtime (#30).\n *\n * The standalone CLI (#20) is a verification toolbox (audit-verify,\n * verify-provenance, calibration-refit, …) — it intentionally does NOT\n * expose a `start` subcommand. The container therefore needs its own\n * PID-1 process whose job is narrow: parse env, build a JSON-line logger,\n * call {@link createTestIntelligenceServer}, install signal handlers,\n * and exit cleanly. No business logic lives here.\n *\n * The entrypoint is wired into the image as:\n *\n * ENTRYPOINT [\"node\", \"/opt/test-intelligence/dist/server-entrypoint.js\"]\n *\n * Behaviour rules:\n *\n * - Defaults to binding on `0.0.0.0:1983` (the container is its own\n * network namespace; loopback-only binding would prevent the operator\n * from publishing the port via `-p`). The host system is still\n * expected to bind the published port to loopback only.\n *\n * - SIGTERM / SIGINT trigger a graceful close. The process exits 0\n * once the underlying `http.Server` has finished its drain, or after\n * {@link SHUTDOWN_GRACE_MS} have elapsed (whichever is sooner).\n *\n * - Bind errors (`EADDRINUSE`, malformed host) exit 1 with a\n * structured log line; no stack trace is leaked.\n */\n\nimport { realpathSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { createWorkspaceLogger } from \"@oscharko-dev/ti-security\";\nimport { createTestIntelligenceServer } from \"./server.js\";\n\n/** Maximum drain budget after SIGTERM before forcing a hard exit. */\nexport const SHUTDOWN_GRACE_MS: number = 10_000;\n\n/** IANA upper bound on a TCP port. */\nconst MAX_TCP_PORT: number = 65_535;\n\n/** Default values applied when no env overrides are present. */\nconst CONTAINER_DEFAULTS = {\n host: \"0.0.0.0\",\n port: 1983,\n requestsPerMinute: 60,\n logFormat: \"json\" as const,\n logLabel: \"test-intelligence\",\n};\n\n/** Resolved configuration shape consumed by {@link runServerEntrypoint}. */\nexport interface ServerEntrypointConfig {\n readonly host: string;\n readonly port: number;\n readonly requestsPerMinute: number;\n readonly allowedCorsOrigins: ReadonlyArray<string>;\n readonly bearerToken: string | undefined;\n readonly logFormat: \"json\" | \"text\";\n readonly logLabel: string;\n}\n\n/** Thrown when an operator-supplied env var is malformed. */\nexport class ServerEntrypointConfigError extends Error {\n public readonly envName: string;\n\n public constructor(envName: string, reason: string) {\n super(`${envName}: ${reason}`);\n this.name = \"ServerEntrypointConfigError\";\n this.envName = envName;\n }\n}\n\nconst parseIntegerEnv = ({\n raw,\n envName,\n min,\n max,\n}: {\n raw: string;\n envName: string;\n min: number;\n max: number;\n}): number => {\n if (!/^-?\\d+$/.test(raw)) {\n throw new ServerEntrypointConfigError(\n envName,\n `expected an integer, received \"${raw}\"`,\n );\n }\n const value = Number.parseInt(raw, 10);\n if (value < min || value > max) {\n throw new ServerEntrypointConfigError(\n envName,\n `must be in [${String(min)}, ${String(max)}], received ${String(value)}`,\n );\n }\n return value;\n};\n\nconst parseLogFormat = (raw: string): \"json\" | \"text\" => {\n if (raw === \"json\" || raw === \"text\") {\n return raw;\n }\n throw new ServerEntrypointConfigError(\n \"TEST_INTELLIGENCE_LOG_FORMAT\",\n `expected \"json\" or \"text\", received \"${raw}\"`,\n );\n};\n\nconst parseCorsOrigins = (raw: string): ReadonlyArray<string> =>\n raw\n .split(\",\")\n .map((segment) => segment.trim())\n .filter((segment) => segment.length > 0);\n\n/**\n * Resolve the runtime configuration from environment variables.\n *\n * Pure function — does no I/O; the test seam is the `env` argument.\n */\nexport const parseServerEntrypointConfig = (\n env: NodeJS.ProcessEnv,\n): ServerEntrypointConfig => {\n const hostRaw = env[\"TEST_INTELLIGENCE_HOST\"];\n const portRaw = env[\"TEST_INTELLIGENCE_PORT\"];\n const rpmRaw = env[\"TEST_INTELLIGENCE_REQUESTS_PER_MINUTE\"];\n const corsRaw = env[\"TEST_INTELLIGENCE_CORS_ORIGINS\"];\n const bearerRaw = env[\"TEST_INTELLIGENCE_BEARER_TOKEN\"];\n const formatRaw = env[\"TEST_INTELLIGENCE_LOG_FORMAT\"];\n\n const host =\n hostRaw !== undefined && hostRaw.length > 0\n ? hostRaw\n : CONTAINER_DEFAULTS.host;\n\n const port =\n portRaw !== undefined && portRaw.length > 0\n ? parseIntegerEnv({\n raw: portRaw,\n envName: \"TEST_INTELLIGENCE_PORT\",\n min: 0,\n max: MAX_TCP_PORT,\n })\n : CONTAINER_DEFAULTS.port;\n\n const requestsPerMinute =\n rpmRaw !== undefined && rpmRaw.length > 0\n ? parseIntegerEnv({\n raw: rpmRaw,\n envName: \"TEST_INTELLIGENCE_REQUESTS_PER_MINUTE\",\n min: 1,\n max: 1_000_000,\n })\n : CONTAINER_DEFAULTS.requestsPerMinute;\n\n const allowedCorsOrigins =\n corsRaw !== undefined ? parseCorsOrigins(corsRaw) : [];\n\n const bearerToken =\n bearerRaw !== undefined && bearerRaw.length > 0 ? bearerRaw : undefined;\n\n const logFormat =\n formatRaw !== undefined && formatRaw.length > 0\n ? parseLogFormat(formatRaw)\n : CONTAINER_DEFAULTS.logFormat;\n\n return {\n host,\n port,\n requestsPerMinute,\n allowedCorsOrigins,\n bearerToken,\n logFormat,\n logLabel: CONTAINER_DEFAULTS.logLabel,\n };\n};\n\n/** Help text printed when the entrypoint is invoked with `--help`. */\nexport const CONTAINER_HELP_TEXT: string = `test-intelligence — container entrypoint for the standalone HTTP server.\n\nUsage:\n test-intelligence [--help|-h|help]\n\nThe container reads its configuration from environment variables:\n\n TEST_INTELLIGENCE_HOST Bind host (default 0.0.0.0 inside the container).\n TEST_INTELLIGENCE_PORT Bind port (default 1983; 0 = ephemeral).\n TEST_INTELLIGENCE_REQUESTS_PER_MINUTE Per-client rate-limit budget (default 60).\n TEST_INTELLIGENCE_CORS_ORIGINS Comma-separated allowed CORS origins.\n TEST_INTELLIGENCE_BEARER_TOKEN Operator bearer token (no default).\n TEST_INTELLIGENCE_LOG_FORMAT \"json\" (default) or \"text\".\n\nThe server exposes these standalone routes:\n\n GET /healthz, GET /readyz, GET /openapi.json, GET /api/v1/*\n\nSignals:\n SIGTERM / SIGINT trigger a graceful close (drain budget 10s).\n`;\n\n/** Return a copy of {@link CONTAINER_HELP_TEXT}. */\nexport const renderContainerHelp = (): string => CONTAINER_HELP_TEXT;\n\n/** Match the canonical help-flag variants. */\nexport const isContainerHelpFlag = (argv: ReadonlyArray<string>): boolean => {\n const first = argv[0];\n return first === \"--help\" || first === \"-h\" || first === \"help\";\n};\n\n/** Result returned by {@link runServerEntrypoint}. */\nexport interface ServerEntrypointResult {\n readonly exitCode: number;\n readonly action: \"help\" | \"served\" | \"config-error\" | \"bind-error\";\n}\n\n/** Test-seam side effects for {@link runServerEntrypoint}. */\nexport interface ServerEntrypointIO {\n readonly stdout: (line: string) => void;\n readonly stderr: (line: string) => void;\n}\n\nconst DEFAULT_IO: ServerEntrypointIO = {\n stdout: (line) => {\n process.stdout.write(line);\n },\n stderr: (line) => {\n process.stderr.write(line);\n },\n};\n\n/**\n * Bootstrap the container entrypoint. Resolves to the intended exit code.\n *\n * The function returns rather than calling `process.exit` directly so it\n * is unit-testable and so the production wrapper at the bottom of this\n * file can flush the logger before terminating.\n */\nexport const runServerEntrypoint = async ({\n argv,\n env,\n io = DEFAULT_IO,\n onReady,\n}: {\n argv: ReadonlyArray<string>;\n env: NodeJS.ProcessEnv;\n io?: ServerEntrypointIO;\n /** Test hook: called after the server has bound. Receives the live server. */\n onReady?: (handle: {\n close: () => Promise<void>;\n host: string;\n port: number;\n }) => Promise<void> | void;\n}): Promise<ServerEntrypointResult> => {\n if (isContainerHelpFlag(argv)) {\n io.stdout(renderContainerHelp());\n return { exitCode: 0, action: \"help\" };\n }\n\n let config: ServerEntrypointConfig;\n try {\n config = parseServerEntrypointConfig(env);\n } catch (error) {\n if (error instanceof ServerEntrypointConfigError) {\n io.stderr(`error: ${error.message}\\n`);\n return { exitCode: 1, action: \"config-error\" };\n }\n throw error;\n }\n\n const logger = createWorkspaceLogger({\n format: config.logFormat,\n label: config.logLabel,\n });\n\n let serverHandle: Awaited<ReturnType<typeof createTestIntelligenceServer>>;\n try {\n serverHandle = await createTestIntelligenceServer({\n host: config.host,\n port: config.port,\n logger,\n requestsPerMinute: config.requestsPerMinute,\n allowedCorsOrigins: config.allowedCorsOrigins,\n ...(config.bearerToken !== undefined\n ? { bearerToken: config.bearerToken }\n : {}),\n env,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logger.log({\n level: \"error\",\n message: `server failed to start: ${message}`,\n event: \"server_start_failed\",\n });\n return { exitCode: 1, action: \"bind-error\" };\n }\n\n logger.log({\n level: \"info\",\n message: `listening on ${serverHandle.url}`,\n event: \"server_started\",\n });\n\n if (onReady) {\n await onReady({\n close: serverHandle.close,\n host: serverHandle.host,\n port: serverHandle.port,\n });\n return { exitCode: 0, action: \"served\" };\n }\n\n await waitForShutdown(serverHandle, logger);\n return { exitCode: 0, action: \"served\" };\n};\n\nconst waitForShutdown = (\n serverHandle: {\n close: () => Promise<void>;\n },\n logger: ReturnType<typeof createWorkspaceLogger>,\n): Promise<void> =>\n new Promise<void>((resolve) => {\n let shuttingDown = false;\n const onSignal = (signal: NodeJS.Signals): void => {\n if (shuttingDown) {\n return;\n }\n shuttingDown = true;\n logger.log({\n level: \"info\",\n message: `received ${signal}; draining`,\n event: \"server_shutdown_started\",\n });\n const hardExit = setTimeout(() => {\n logger.log({\n level: \"warn\",\n message: `drain budget ${String(SHUTDOWN_GRACE_MS)}ms exceeded`,\n event: \"server_shutdown_force\",\n });\n resolve();\n }, SHUTDOWN_GRACE_MS);\n hardExit.unref();\n serverHandle\n .close()\n .then(() => {\n clearTimeout(hardExit);\n logger.log({\n level: \"info\",\n message: \"drained\",\n event: \"server_shutdown_completed\",\n });\n resolve();\n })\n .catch((error: unknown) => {\n clearTimeout(hardExit);\n const message =\n error instanceof Error ? error.message : String(error);\n logger.log({\n level: \"error\",\n message: `drain failed: ${message}`,\n event: \"server_shutdown_failed\",\n });\n resolve();\n });\n };\n process.once(\"SIGTERM\", onSignal);\n process.once(\"SIGINT\", onSignal);\n });\n\n/**\n * True when the module URL matches the realpath-resolved file URL of\n * `entry`. Exported for unit-test coverage of the symlink-aware path.\n *\n * `import.meta.url` (which the production wrapper passes in) is the\n * realpath-resolved file URL Node assigns to the entry module; on\n * systems where the invocation path traverses a symlink (e.g.\n * `/tmp -> /private/tmp` on macOS), the raw `process.argv[1]` form does\n * not match. Realpath-resolve before converting, then percent-encode\n * via {@link pathToFileURL}.\n */\nexport const isEntryMatchingModuleUrl = ({\n entry,\n moduleUrl,\n}: {\n entry: string | undefined;\n moduleUrl: string;\n}): boolean => {\n if (entry === undefined) {\n return false;\n }\n let resolved: string;\n try {\n resolved = realpathSync(entry);\n } catch {\n return false;\n }\n return moduleUrl === pathToFileURL(resolved).href;\n};\n\nif (\n isEntryMatchingModuleUrl({\n entry: process.argv[1],\n moduleUrl: import.meta.url,\n })\n) {\n void runServerEntrypoint({ argv: process.argv.slice(2), env: process.env })\n .then((result) => {\n process.exit(result.exitCode);\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`fatal: ${message}\\n`);\n process.exit(1);\n });\n}\n"]}