@suveren/gateway 0.18.0 → 0.18.2
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.
- package/bin/suveren-gateway.js +26 -1
- package/content/guides/3-mandates.md +1 -0
- package/dist/control-plane/index.mjs +132 -12
- package/dist/mcp-server/chunk-QOKPECVY.mjs +751 -0
- package/dist/mcp-server/http.mjs +1626 -190
- package/dist/mcp-server/report-verify-cli.mjs +171 -0
- package/dist/ui/assets/{index-y1OFeYnS.css → index-Bfi-G4qJ.css} +1 -1
- package/dist/ui/assets/index-D0uXCqQB.js +111 -0
- package/dist/ui/index.html +2 -2
- package/node_modules/dayjs/.editorconfig +7 -0
- package/node_modules/dayjs/CHANGELOG.md +1022 -0
- package/node_modules/dayjs/LICENSE +21 -0
- package/node_modules/dayjs/README.md +186 -0
- package/node_modules/dayjs/dayjs.min.js +1 -0
- package/node_modules/dayjs/esm/constant.js +25 -0
- package/node_modules/dayjs/esm/index.d.ts +429 -0
- package/node_modules/dayjs/esm/index.js +541 -0
- package/node_modules/dayjs/esm/locale/af.js +39 -0
- package/node_modules/dayjs/esm/locale/am.js +40 -0
- package/node_modules/dayjs/esm/locale/ar-dz.js +41 -0
- package/node_modules/dayjs/esm/locale/ar-iq.js +42 -0
- package/node_modules/dayjs/esm/locale/ar-kw.js +41 -0
- package/node_modules/dayjs/esm/locale/ar-ly.js +27 -0
- package/node_modules/dayjs/esm/locale/ar-ma.js +42 -0
- package/node_modules/dayjs/esm/locale/ar-sa.js +41 -0
- package/node_modules/dayjs/esm/locale/ar-tn.js +42 -0
- package/node_modules/dayjs/esm/locale/ar.js +81 -0
- package/node_modules/dayjs/esm/locale/az.js +39 -0
- package/node_modules/dayjs/esm/locale/be.js +102 -0
- package/node_modules/dayjs/esm/locale/bg.js +55 -0
- package/node_modules/dayjs/esm/locale/bi.js +39 -0
- package/node_modules/dayjs/esm/locale/bm.js +39 -0
- package/node_modules/dayjs/esm/locale/bn-bd.js +81 -0
- package/node_modules/dayjs/esm/locale/bn.js +72 -0
- package/node_modules/dayjs/esm/locale/bo.js +38 -0
- package/node_modules/dayjs/esm/locale/br.js +93 -0
- package/node_modules/dayjs/esm/locale/bs.js +24 -0
- package/node_modules/dayjs/esm/locale/ca.js +44 -0
- package/node_modules/dayjs/esm/locale/cs.js +120 -0
- package/node_modules/dayjs/esm/locale/cv.js +24 -0
- package/node_modules/dayjs/esm/locale/cy.js +39 -0
- package/node_modules/dayjs/esm/locale/da.js +40 -0
- package/node_modules/dayjs/esm/locale/de-at.js +63 -0
- package/node_modules/dayjs/esm/locale/de-ch.js +63 -0
- package/node_modules/dayjs/esm/locale/de.js +64 -0
- package/node_modules/dayjs/esm/locale/dv.js +39 -0
- package/node_modules/dayjs/esm/locale/el.js +39 -0
- package/node_modules/dayjs/esm/locale/en-au.js +41 -0
- package/node_modules/dayjs/esm/locale/en-ca.js +38 -0
- package/node_modules/dayjs/esm/locale/en-gb.js +42 -0
- package/node_modules/dayjs/esm/locale/en-ie.js +39 -0
- package/node_modules/dayjs/esm/locale/en-il.js +38 -0
- package/node_modules/dayjs/esm/locale/en-in.js +42 -0
- package/node_modules/dayjs/esm/locale/en-nz.js +41 -0
- package/node_modules/dayjs/esm/locale/en-sg.js +39 -0
- package/node_modules/dayjs/esm/locale/en-tt.js +42 -0
- package/node_modules/dayjs/esm/locale/en.js +12 -0
- package/node_modules/dayjs/esm/locale/eo.js +39 -0
- package/node_modules/dayjs/esm/locale/es-do.js +39 -0
- package/node_modules/dayjs/esm/locale/es-mx.js +38 -0
- package/node_modules/dayjs/esm/locale/es-pr.js +39 -0
- package/node_modules/dayjs/esm/locale/es-us.js +38 -0
- package/node_modules/dayjs/esm/locale/es.js +39 -0
- package/node_modules/dayjs/esm/locale/et.js +65 -0
- package/node_modules/dayjs/esm/locale/eu.js +43 -0
- package/node_modules/dayjs/esm/locale/fa.js +39 -0
- package/node_modules/dayjs/esm/locale/fi.js +88 -0
- package/node_modules/dayjs/esm/locale/fo.js +39 -0
- package/node_modules/dayjs/esm/locale/fr-ca.js +38 -0
- package/node_modules/dayjs/esm/locale/fr-ch.js +39 -0
- package/node_modules/dayjs/esm/locale/fr.js +41 -0
- package/node_modules/dayjs/esm/locale/fy.js +39 -0
- package/node_modules/dayjs/esm/locale/ga.js +39 -0
- package/node_modules/dayjs/esm/locale/gd.js +39 -0
- package/node_modules/dayjs/esm/locale/gl.js +39 -0
- package/node_modules/dayjs/esm/locale/gom-latn.js +25 -0
- package/node_modules/dayjs/esm/locale/gu.js +38 -0
- package/node_modules/dayjs/esm/locale/he.js +78 -0
- package/node_modules/dayjs/esm/locale/hi.js +38 -0
- package/node_modules/dayjs/esm/locale/hr.js +53 -0
- package/node_modules/dayjs/esm/locale/ht.js +38 -0
- package/node_modules/dayjs/esm/locale/hu.js +61 -0
- package/node_modules/dayjs/esm/locale/hy-am.js +39 -0
- package/node_modules/dayjs/esm/locale/id.js +39 -0
- package/node_modules/dayjs/esm/locale/index.d.ts +11 -0
- package/node_modules/dayjs/esm/locale/is.js +68 -0
- package/node_modules/dayjs/esm/locale/it-ch.js +39 -0
- package/node_modules/dayjs/esm/locale/it.js +39 -0
- package/node_modules/dayjs/esm/locale/ja.js +45 -0
- package/node_modules/dayjs/esm/locale/jv.js +39 -0
- package/node_modules/dayjs/esm/locale/ka.js +39 -0
- package/node_modules/dayjs/esm/locale/kk.js +39 -0
- package/node_modules/dayjs/esm/locale/km.js +42 -0
- package/node_modules/dayjs/esm/locale/kn.js +38 -0
- package/node_modules/dayjs/esm/locale/ko.js +45 -0
- package/node_modules/dayjs/esm/locale/ku.js +77 -0
- package/node_modules/dayjs/esm/locale/ky.js +39 -0
- package/node_modules/dayjs/esm/locale/lb.js +24 -0
- package/node_modules/dayjs/esm/locale/lo.js +38 -0
- package/node_modules/dayjs/esm/locale/lt.js +70 -0
- package/node_modules/dayjs/esm/locale/lv.js +39 -0
- package/node_modules/dayjs/esm/locale/me.js +24 -0
- package/node_modules/dayjs/esm/locale/mi.js +39 -0
- package/node_modules/dayjs/esm/locale/mk.js +39 -0
- package/node_modules/dayjs/esm/locale/ml.js +38 -0
- package/node_modules/dayjs/esm/locale/mn.js +38 -0
- package/node_modules/dayjs/esm/locale/mr.js +23 -0
- package/node_modules/dayjs/esm/locale/ms-my.js +39 -0
- package/node_modules/dayjs/esm/locale/ms.js +39 -0
- package/node_modules/dayjs/esm/locale/mt.js +39 -0
- package/node_modules/dayjs/esm/locale/my.js +39 -0
- package/node_modules/dayjs/esm/locale/nb.js +40 -0
- package/node_modules/dayjs/esm/locale/ne.js +40 -0
- package/node_modules/dayjs/esm/locale/nl-be.js +39 -0
- package/node_modules/dayjs/esm/locale/nl.js +40 -0
- package/node_modules/dayjs/esm/locale/nn.js +39 -0
- package/node_modules/dayjs/esm/locale/oc-lnc.js +39 -0
- package/node_modules/dayjs/esm/locale/pa-in.js +38 -0
- package/node_modules/dayjs/esm/locale/pl.js +87 -0
- package/node_modules/dayjs/esm/locale/pt-br.js +38 -0
- package/node_modules/dayjs/esm/locale/pt.js +40 -0
- package/node_modules/dayjs/esm/locale/rn.js +39 -0
- package/node_modules/dayjs/esm/locale/ro.js +39 -0
- package/node_modules/dayjs/esm/locale/ru.js +99 -0
- package/node_modules/dayjs/esm/locale/rw.js +35 -0
- package/node_modules/dayjs/esm/locale/sd.js +39 -0
- package/node_modules/dayjs/esm/locale/se.js +39 -0
- package/node_modules/dayjs/esm/locale/si.js +38 -0
- package/node_modules/dayjs/esm/locale/sk.js +121 -0
- package/node_modules/dayjs/esm/locale/sl.js +141 -0
- package/node_modules/dayjs/esm/locale/sq.js +39 -0
- package/node_modules/dayjs/esm/locale/sr-cyrl.js +74 -0
- package/node_modules/dayjs/esm/locale/sr.js +74 -0
- package/node_modules/dayjs/esm/locale/ss.js +39 -0
- package/node_modules/dayjs/esm/locale/sv-fi.js +46 -0
- package/node_modules/dayjs/esm/locale/sv.js +44 -0
- package/node_modules/dayjs/esm/locale/sw.js +39 -0
- package/node_modules/dayjs/esm/locale/ta.js +38 -0
- package/node_modules/dayjs/esm/locale/te.js +38 -0
- package/node_modules/dayjs/esm/locale/tet.js +39 -0
- package/node_modules/dayjs/esm/locale/tg.js +39 -0
- package/node_modules/dayjs/esm/locale/th.js +38 -0
- package/node_modules/dayjs/esm/locale/tk.js +39 -0
- package/node_modules/dayjs/esm/locale/tl-ph.js +39 -0
- package/node_modules/dayjs/esm/locale/tlh.js +24 -0
- package/node_modules/dayjs/esm/locale/tr.js +39 -0
- package/node_modules/dayjs/esm/locale/types.d.ts +33 -0
- package/node_modules/dayjs/esm/locale/tzl.js +24 -0
- package/node_modules/dayjs/esm/locale/tzm-latn.js +39 -0
- package/node_modules/dayjs/esm/locale/tzm.js +39 -0
- package/node_modules/dayjs/esm/locale/ug-cn.js +39 -0
- package/node_modules/dayjs/esm/locale/uk.js +77 -0
- package/node_modules/dayjs/esm/locale/ur.js +39 -0
- package/node_modules/dayjs/esm/locale/uz-latn.js +39 -0
- package/node_modules/dayjs/esm/locale/uz.js +39 -0
- package/node_modules/dayjs/esm/locale/vi.js +43 -0
- package/node_modules/dayjs/esm/locale/x-pseudo.js +39 -0
- package/node_modules/dayjs/esm/locale/yo.js +39 -0
- package/node_modules/dayjs/esm/locale/zh-cn.js +67 -0
- package/node_modules/dayjs/esm/locale/zh-hk.js +65 -0
- package/node_modules/dayjs/esm/locale/zh-tw.js +65 -0
- package/node_modules/dayjs/esm/locale/zh.js +67 -0
- package/node_modules/dayjs/esm/plugin/advancedFormat/index.d.ts +4 -0
- package/node_modules/dayjs/esm/plugin/advancedFormat/index.js +66 -0
- package/node_modules/dayjs/esm/plugin/arraySupport/index.d.ts +10 -0
- package/node_modules/dayjs/esm/plugin/arraySupport/index.js +33 -0
- package/node_modules/dayjs/esm/plugin/badMutable/index.d.ts +4 -0
- package/node_modules/dayjs/esm/plugin/badMutable/index.js +61 -0
- package/node_modules/dayjs/esm/plugin/bigIntSupport/index.d.ts +11 -0
- package/node_modules/dayjs/esm/plugin/bigIntSupport/index.js +32 -0
- package/node_modules/dayjs/esm/plugin/buddhistEra/index.d.ts +4 -0
- package/node_modules/dayjs/esm/plugin/buddhistEra/index.js +21 -0
- package/node_modules/dayjs/esm/plugin/calendar/index.d.ts +10 -0
- package/node_modules/dayjs/esm/plugin/calendar/index.js +32 -0
- package/node_modules/dayjs/esm/plugin/customParseFormat/index.d.ts +8 -0
- package/node_modules/dayjs/esm/plugin/customParseFormat/index.js +333 -0
- package/node_modules/dayjs/esm/plugin/dayOfYear/index.d.ts +11 -0
- package/node_modules/dayjs/esm/plugin/dayOfYear/index.js +9 -0
- package/node_modules/dayjs/esm/plugin/devHelper/index.d.ts +4 -0
- package/node_modules/dayjs/esm/plugin/devHelper/index.js +50 -0
- package/node_modules/dayjs/esm/plugin/duration/index.d.ts +88 -0
- package/node_modules/dayjs/esm/plugin/duration/index.js +357 -0
- package/node_modules/dayjs/esm/plugin/isBetween/index.d.ts +10 -0
- package/node_modules/dayjs/esm/plugin/isBetween/index.js +10 -0
- package/node_modules/dayjs/esm/plugin/isLeapYear/index.d.ts +10 -0
- package/node_modules/dayjs/esm/plugin/isLeapYear/index.js +7 -0
- package/node_modules/dayjs/esm/plugin/isMoment/index.d.ts +10 -0
- package/node_modules/dayjs/esm/plugin/isMoment/index.js +5 -0
- package/node_modules/dayjs/esm/plugin/isSameOrAfter/index.d.ts +10 -0
- package/node_modules/dayjs/esm/plugin/isSameOrAfter/index.js +5 -0
- package/node_modules/dayjs/esm/plugin/isSameOrBefore/index.d.ts +10 -0
- package/node_modules/dayjs/esm/plugin/isSameOrBefore/index.js +5 -0
- package/node_modules/dayjs/esm/plugin/isToday/index.d.ts +10 -0
- package/node_modules/dayjs/esm/plugin/isToday/index.js +9 -0
- package/node_modules/dayjs/esm/plugin/isTomorrow/index.d.ts +10 -0
- package/node_modules/dayjs/esm/plugin/isTomorrow/index.js +9 -0
- package/node_modules/dayjs/esm/plugin/isYesterday/index.d.ts +10 -0
- package/node_modules/dayjs/esm/plugin/isYesterday/index.js +9 -0
- package/node_modules/dayjs/esm/plugin/isoWeek/index.d.ts +27 -0
- package/node_modules/dayjs/esm/plugin/isoWeek/index.js +57 -0
- package/node_modules/dayjs/esm/plugin/isoWeeksInYear/index.d.ts +10 -0
- package/node_modules/dayjs/esm/plugin/isoWeeksInYear/index.js +15 -0
- package/node_modules/dayjs/esm/plugin/localeData/index.d.ts +44 -0
- package/node_modules/dayjs/esm/plugin/localeData/index.js +114 -0
- package/node_modules/dayjs/esm/plugin/localizedFormat/index.d.ts +4 -0
- package/node_modules/dayjs/esm/plugin/localizedFormat/index.js +20 -0
- package/node_modules/dayjs/esm/plugin/localizedFormat/utils.js +20 -0
- package/node_modules/dayjs/esm/plugin/minMax/index.d.ts +22 -0
- package/node_modules/dayjs/esm/plugin/minMax/index.js +39 -0
- package/node_modules/dayjs/esm/plugin/negativeYear/index.d.ts +4 -0
- package/node_modules/dayjs/esm/plugin/negativeYear/index.js +36 -0
- package/node_modules/dayjs/esm/plugin/objectSupport/index.d.ts +48 -0
- package/node_modules/dayjs/esm/plugin/objectSupport/index.js +97 -0
- package/node_modules/dayjs/esm/plugin/pluralGetSet/index.d.ts +44 -0
- package/node_modules/dayjs/esm/plugin/pluralGetSet/index.js +7 -0
- package/node_modules/dayjs/esm/plugin/preParsePostFormat/index.d.ts +4 -0
- package/node_modules/dayjs/esm/plugin/preParsePostFormat/index.js +40 -0
- package/node_modules/dayjs/esm/plugin/quarterOfYear/index.d.ts +26 -0
- package/node_modules/dayjs/esm/plugin/quarterOfYear/index.js +41 -0
- package/node_modules/dayjs/esm/plugin/relativeTime/index.d.ts +24 -0
- package/node_modules/dayjs/esm/plugin/relativeTime/index.js +130 -0
- package/node_modules/dayjs/esm/plugin/timezone/index.d.ts +20 -0
- package/node_modules/dayjs/esm/plugin/timezone/index.js +205 -0
- package/node_modules/dayjs/esm/plugin/toArray/index.d.ts +10 -0
- package/node_modules/dayjs/esm/plugin/toArray/index.js +7 -0
- package/node_modules/dayjs/esm/plugin/toObject/index.d.ts +20 -0
- package/node_modules/dayjs/esm/plugin/toObject/index.js +15 -0
- package/node_modules/dayjs/esm/plugin/updateLocale/index.d.ts +8 -0
- package/node_modules/dayjs/esm/plugin/updateLocale/index.js +18 -0
- package/node_modules/dayjs/esm/plugin/utc/index.d.ts +19 -0
- package/node_modules/dayjs/esm/plugin/utc/index.js +188 -0
- package/node_modules/dayjs/esm/plugin/weekOfYear/index.d.ts +12 -0
- package/node_modules/dayjs/esm/plugin/weekOfYear/index.js +44 -0
- package/node_modules/dayjs/esm/plugin/weekYear/index.d.ts +10 -0
- package/node_modules/dayjs/esm/plugin/weekYear/index.js +19 -0
- package/node_modules/dayjs/esm/plugin/weekday/index.d.ts +12 -0
- package/node_modules/dayjs/esm/plugin/weekday/index.js +15 -0
- package/node_modules/dayjs/esm/utils.js +58 -0
- package/node_modules/dayjs/index.d.ts +429 -0
- package/node_modules/dayjs/locale/af.js +1 -0
- package/node_modules/dayjs/locale/am.js +1 -0
- package/node_modules/dayjs/locale/ar-dz.js +1 -0
- package/node_modules/dayjs/locale/ar-iq.js +1 -0
- package/node_modules/dayjs/locale/ar-kw.js +1 -0
- package/node_modules/dayjs/locale/ar-ly.js +1 -0
- package/node_modules/dayjs/locale/ar-ma.js +1 -0
- package/node_modules/dayjs/locale/ar-sa.js +1 -0
- package/node_modules/dayjs/locale/ar-tn.js +1 -0
- package/node_modules/dayjs/locale/ar.js +1 -0
- package/node_modules/dayjs/locale/az.js +1 -0
- package/node_modules/dayjs/locale/be.js +1 -0
- package/node_modules/dayjs/locale/bg.js +1 -0
- package/node_modules/dayjs/locale/bi.js +1 -0
- package/node_modules/dayjs/locale/bm.js +1 -0
- package/node_modules/dayjs/locale/bn-bd.js +1 -0
- package/node_modules/dayjs/locale/bn.js +1 -0
- package/node_modules/dayjs/locale/bo.js +1 -0
- package/node_modules/dayjs/locale/br.js +1 -0
- package/node_modules/dayjs/locale/bs.js +1 -0
- package/node_modules/dayjs/locale/ca.js +1 -0
- package/node_modules/dayjs/locale/cs.js +1 -0
- package/node_modules/dayjs/locale/cv.js +1 -0
- package/node_modules/dayjs/locale/cy.js +1 -0
- package/node_modules/dayjs/locale/da.js +1 -0
- package/node_modules/dayjs/locale/de-at.js +1 -0
- package/node_modules/dayjs/locale/de-ch.js +1 -0
- package/node_modules/dayjs/locale/de.js +1 -0
- package/node_modules/dayjs/locale/dv.js +1 -0
- package/node_modules/dayjs/locale/el.js +1 -0
- package/node_modules/dayjs/locale/en-au.js +1 -0
- package/node_modules/dayjs/locale/en-ca.js +1 -0
- package/node_modules/dayjs/locale/en-gb.js +1 -0
- package/node_modules/dayjs/locale/en-ie.js +1 -0
- package/node_modules/dayjs/locale/en-il.js +1 -0
- package/node_modules/dayjs/locale/en-in.js +1 -0
- package/node_modules/dayjs/locale/en-nz.js +1 -0
- package/node_modules/dayjs/locale/en-sg.js +1 -0
- package/node_modules/dayjs/locale/en-tt.js +1 -0
- package/node_modules/dayjs/locale/en.js +1 -0
- package/node_modules/dayjs/locale/eo.js +1 -0
- package/node_modules/dayjs/locale/es-do.js +1 -0
- package/node_modules/dayjs/locale/es-mx.js +1 -0
- package/node_modules/dayjs/locale/es-pr.js +1 -0
- package/node_modules/dayjs/locale/es-us.js +1 -0
- package/node_modules/dayjs/locale/es.js +1 -0
- package/node_modules/dayjs/locale/et.js +1 -0
- package/node_modules/dayjs/locale/eu.js +1 -0
- package/node_modules/dayjs/locale/fa.js +1 -0
- package/node_modules/dayjs/locale/fi.js +1 -0
- package/node_modules/dayjs/locale/fo.js +1 -0
- package/node_modules/dayjs/locale/fr-ca.js +1 -0
- package/node_modules/dayjs/locale/fr-ch.js +1 -0
- package/node_modules/dayjs/locale/fr.js +1 -0
- package/node_modules/dayjs/locale/fy.js +1 -0
- package/node_modules/dayjs/locale/ga.js +1 -0
- package/node_modules/dayjs/locale/gd.js +1 -0
- package/node_modules/dayjs/locale/gl.js +1 -0
- package/node_modules/dayjs/locale/gom-latn.js +1 -0
- package/node_modules/dayjs/locale/gu.js +1 -0
- package/node_modules/dayjs/locale/he.js +1 -0
- package/node_modules/dayjs/locale/hi.js +1 -0
- package/node_modules/dayjs/locale/hr.js +1 -0
- package/node_modules/dayjs/locale/ht.js +1 -0
- package/node_modules/dayjs/locale/hu.js +1 -0
- package/node_modules/dayjs/locale/hy-am.js +1 -0
- package/node_modules/dayjs/locale/id.js +1 -0
- package/node_modules/dayjs/locale/index.d.ts +11 -0
- package/node_modules/dayjs/locale/is.js +1 -0
- package/node_modules/dayjs/locale/it-ch.js +1 -0
- package/node_modules/dayjs/locale/it.js +1 -0
- package/node_modules/dayjs/locale/ja.js +1 -0
- package/node_modules/dayjs/locale/jv.js +1 -0
- package/node_modules/dayjs/locale/ka.js +1 -0
- package/node_modules/dayjs/locale/kk.js +1 -0
- package/node_modules/dayjs/locale/km.js +1 -0
- package/node_modules/dayjs/locale/kn.js +1 -0
- package/node_modules/dayjs/locale/ko.js +1 -0
- package/node_modules/dayjs/locale/ku.js +1 -0
- package/node_modules/dayjs/locale/ky.js +1 -0
- package/node_modules/dayjs/locale/lb.js +1 -0
- package/node_modules/dayjs/locale/lo.js +1 -0
- package/node_modules/dayjs/locale/lt.js +1 -0
- package/node_modules/dayjs/locale/lv.js +1 -0
- package/node_modules/dayjs/locale/me.js +1 -0
- package/node_modules/dayjs/locale/mi.js +1 -0
- package/node_modules/dayjs/locale/mk.js +1 -0
- package/node_modules/dayjs/locale/ml.js +1 -0
- package/node_modules/dayjs/locale/mn.js +1 -0
- package/node_modules/dayjs/locale/mr.js +1 -0
- package/node_modules/dayjs/locale/ms-my.js +1 -0
- package/node_modules/dayjs/locale/ms.js +1 -0
- package/node_modules/dayjs/locale/mt.js +1 -0
- package/node_modules/dayjs/locale/my.js +1 -0
- package/node_modules/dayjs/locale/nb.js +1 -0
- package/node_modules/dayjs/locale/ne.js +1 -0
- package/node_modules/dayjs/locale/nl-be.js +1 -0
- package/node_modules/dayjs/locale/nl.js +1 -0
- package/node_modules/dayjs/locale/nn.js +1 -0
- package/node_modules/dayjs/locale/oc-lnc.js +1 -0
- package/node_modules/dayjs/locale/pa-in.js +1 -0
- package/node_modules/dayjs/locale/pl.js +1 -0
- package/node_modules/dayjs/locale/pt-br.js +1 -0
- package/node_modules/dayjs/locale/pt.js +1 -0
- package/node_modules/dayjs/locale/rn.js +1 -0
- package/node_modules/dayjs/locale/ro.js +1 -0
- package/node_modules/dayjs/locale/ru.js +1 -0
- package/node_modules/dayjs/locale/rw.js +1 -0
- package/node_modules/dayjs/locale/sd.js +1 -0
- package/node_modules/dayjs/locale/se.js +1 -0
- package/node_modules/dayjs/locale/si.js +1 -0
- package/node_modules/dayjs/locale/sk.js +1 -0
- package/node_modules/dayjs/locale/sl.js +1 -0
- package/node_modules/dayjs/locale/sq.js +1 -0
- package/node_modules/dayjs/locale/sr-cyrl.js +1 -0
- package/node_modules/dayjs/locale/sr.js +1 -0
- package/node_modules/dayjs/locale/ss.js +1 -0
- package/node_modules/dayjs/locale/sv-fi.js +1 -0
- package/node_modules/dayjs/locale/sv.js +1 -0
- package/node_modules/dayjs/locale/sw.js +1 -0
- package/node_modules/dayjs/locale/ta.js +1 -0
- package/node_modules/dayjs/locale/te.js +1 -0
- package/node_modules/dayjs/locale/tet.js +1 -0
- package/node_modules/dayjs/locale/tg.js +1 -0
- package/node_modules/dayjs/locale/th.js +1 -0
- package/node_modules/dayjs/locale/tk.js +1 -0
- package/node_modules/dayjs/locale/tl-ph.js +1 -0
- package/node_modules/dayjs/locale/tlh.js +1 -0
- package/node_modules/dayjs/locale/tr.js +1 -0
- package/node_modules/dayjs/locale/types.d.ts +33 -0
- package/node_modules/dayjs/locale/tzl.js +1 -0
- package/node_modules/dayjs/locale/tzm-latn.js +1 -0
- package/node_modules/dayjs/locale/tzm.js +1 -0
- package/node_modules/dayjs/locale/ug-cn.js +1 -0
- package/node_modules/dayjs/locale/uk.js +1 -0
- package/node_modules/dayjs/locale/ur.js +1 -0
- package/node_modules/dayjs/locale/uz-latn.js +1 -0
- package/node_modules/dayjs/locale/uz.js +1 -0
- package/node_modules/dayjs/locale/vi.js +1 -0
- package/node_modules/dayjs/locale/x-pseudo.js +1 -0
- package/node_modules/dayjs/locale/yo.js +1 -0
- package/node_modules/dayjs/locale/zh-cn.js +1 -0
- package/node_modules/dayjs/locale/zh-hk.js +1 -0
- package/node_modules/dayjs/locale/zh-tw.js +1 -0
- package/node_modules/dayjs/locale/zh.js +1 -0
- package/node_modules/dayjs/locale.json +1 -0
- package/node_modules/dayjs/package.json +86 -0
- package/node_modules/dayjs/plugin/advancedFormat.d.ts +4 -0
- package/node_modules/dayjs/plugin/advancedFormat.js +1 -0
- package/node_modules/dayjs/plugin/arraySupport.d.ts +10 -0
- package/node_modules/dayjs/plugin/arraySupport.js +1 -0
- package/node_modules/dayjs/plugin/badMutable.d.ts +4 -0
- package/node_modules/dayjs/plugin/badMutable.js +1 -0
- package/node_modules/dayjs/plugin/bigIntSupport.d.ts +11 -0
- package/node_modules/dayjs/plugin/bigIntSupport.js +1 -0
- package/node_modules/dayjs/plugin/buddhistEra.d.ts +4 -0
- package/node_modules/dayjs/plugin/buddhistEra.js +1 -0
- package/node_modules/dayjs/plugin/calendar.d.ts +10 -0
- package/node_modules/dayjs/plugin/calendar.js +1 -0
- package/node_modules/dayjs/plugin/customParseFormat.d.ts +8 -0
- package/node_modules/dayjs/plugin/customParseFormat.js +1 -0
- package/node_modules/dayjs/plugin/dayOfYear.d.ts +11 -0
- package/node_modules/dayjs/plugin/dayOfYear.js +1 -0
- package/node_modules/dayjs/plugin/devHelper.d.ts +4 -0
- package/node_modules/dayjs/plugin/devHelper.js +1 -0
- package/node_modules/dayjs/plugin/duration.d.ts +88 -0
- package/node_modules/dayjs/plugin/duration.js +1 -0
- package/node_modules/dayjs/plugin/isBetween.d.ts +10 -0
- package/node_modules/dayjs/plugin/isBetween.js +1 -0
- package/node_modules/dayjs/plugin/isLeapYear.d.ts +10 -0
- package/node_modules/dayjs/plugin/isLeapYear.js +1 -0
- package/node_modules/dayjs/plugin/isMoment.d.ts +10 -0
- package/node_modules/dayjs/plugin/isMoment.js +1 -0
- package/node_modules/dayjs/plugin/isSameOrAfter.d.ts +10 -0
- package/node_modules/dayjs/plugin/isSameOrAfter.js +1 -0
- package/node_modules/dayjs/plugin/isSameOrBefore.d.ts +10 -0
- package/node_modules/dayjs/plugin/isSameOrBefore.js +1 -0
- package/node_modules/dayjs/plugin/isToday.d.ts +10 -0
- package/node_modules/dayjs/plugin/isToday.js +1 -0
- package/node_modules/dayjs/plugin/isTomorrow.d.ts +10 -0
- package/node_modules/dayjs/plugin/isTomorrow.js +1 -0
- package/node_modules/dayjs/plugin/isYesterday.d.ts +10 -0
- package/node_modules/dayjs/plugin/isYesterday.js +1 -0
- package/node_modules/dayjs/plugin/isoWeek.d.ts +27 -0
- package/node_modules/dayjs/plugin/isoWeek.js +1 -0
- package/node_modules/dayjs/plugin/isoWeeksInYear.d.ts +10 -0
- package/node_modules/dayjs/plugin/isoWeeksInYear.js +1 -0
- package/node_modules/dayjs/plugin/localeData.d.ts +44 -0
- package/node_modules/dayjs/plugin/localeData.js +1 -0
- package/node_modules/dayjs/plugin/localizedFormat.d.ts +4 -0
- package/node_modules/dayjs/plugin/localizedFormat.js +1 -0
- package/node_modules/dayjs/plugin/minMax.d.ts +22 -0
- package/node_modules/dayjs/plugin/minMax.js +1 -0
- package/node_modules/dayjs/plugin/negativeYear.d.ts +4 -0
- package/node_modules/dayjs/plugin/negativeYear.js +1 -0
- package/node_modules/dayjs/plugin/objectSupport.d.ts +48 -0
- package/node_modules/dayjs/plugin/objectSupport.js +1 -0
- package/node_modules/dayjs/plugin/pluralGetSet.d.ts +44 -0
- package/node_modules/dayjs/plugin/pluralGetSet.js +1 -0
- package/node_modules/dayjs/plugin/preParsePostFormat.d.ts +4 -0
- package/node_modules/dayjs/plugin/preParsePostFormat.js +1 -0
- package/node_modules/dayjs/plugin/quarterOfYear.d.ts +26 -0
- package/node_modules/dayjs/plugin/quarterOfYear.js +1 -0
- package/node_modules/dayjs/plugin/relativeTime.d.ts +24 -0
- package/node_modules/dayjs/plugin/relativeTime.js +1 -0
- package/node_modules/dayjs/plugin/timezone.d.ts +20 -0
- package/node_modules/dayjs/plugin/timezone.js +1 -0
- package/node_modules/dayjs/plugin/toArray.d.ts +10 -0
- package/node_modules/dayjs/plugin/toArray.js +1 -0
- package/node_modules/dayjs/plugin/toObject.d.ts +20 -0
- package/node_modules/dayjs/plugin/toObject.js +1 -0
- package/node_modules/dayjs/plugin/updateLocale.d.ts +8 -0
- package/node_modules/dayjs/plugin/updateLocale.js +1 -0
- package/node_modules/dayjs/plugin/utc.d.ts +19 -0
- package/node_modules/dayjs/plugin/utc.js +1 -0
- package/node_modules/dayjs/plugin/weekOfYear.d.ts +12 -0
- package/node_modules/dayjs/plugin/weekOfYear.js +1 -0
- package/node_modules/dayjs/plugin/weekYear.d.ts +10 -0
- package/node_modules/dayjs/plugin/weekYear.js +1 -0
- package/node_modules/dayjs/plugin/weekday.d.ts +12 -0
- package/node_modules/dayjs/plugin/weekday.js +1 -0
- package/node_modules/deepmerge/.editorconfig +7 -0
- package/node_modules/deepmerge/.eslintcache +1 -0
- package/node_modules/deepmerge/changelog.md +167 -0
- package/node_modules/deepmerge/dist/cjs.js +133 -0
- package/node_modules/deepmerge/dist/umd.js +139 -0
- package/node_modules/deepmerge/index.d.ts +20 -0
- package/node_modules/deepmerge/index.js +106 -0
- package/node_modules/deepmerge/license.txt +21 -0
- package/node_modules/deepmerge/package.json +42 -0
- package/node_modules/deepmerge/readme.md +264 -0
- package/node_modules/deepmerge/rollup.config.js +22 -0
- package/node_modules/dom-serializer/LICENSE +7 -0
- package/node_modules/dom-serializer/README.md +115 -0
- package/node_modules/dom-serializer/dist/foreign-names.d.ts +11 -0
- package/node_modules/dom-serializer/dist/foreign-names.d.ts.map +1 -0
- package/node_modules/dom-serializer/dist/foreign-names.js +15 -0
- package/node_modules/dom-serializer/dist/foreign-names.js.map +1 -0
- package/node_modules/dom-serializer/dist/index.d.ts +72 -0
- package/node_modules/dom-serializer/dist/index.d.ts.map +1 -0
- package/node_modules/dom-serializer/dist/index.js +173 -0
- package/node_modules/dom-serializer/dist/index.js.map +1 -0
- package/node_modules/dom-serializer/package.json +69 -0
- package/node_modules/dom-serializer/src/foreign-names.ts +19 -0
- package/node_modules/dom-serializer/src/index.ts +315 -0
- package/node_modules/domelementtype/LICENSE +11 -0
- package/node_modules/domelementtype/dist/index.d.ts +48 -0
- package/node_modules/domelementtype/dist/index.d.ts.map +1 -0
- package/node_modules/domelementtype/dist/index.js +60 -0
- package/node_modules/domelementtype/package.json +59 -0
- package/node_modules/domelementtype/readme.md +1 -0
- package/node_modules/domhandler/LICENSE +11 -0
- package/node_modules/domhandler/dist/index.d.ts +79 -0
- package/node_modules/domhandler/dist/index.d.ts.map +1 -0
- package/node_modules/domhandler/dist/index.js +155 -0
- package/node_modules/domhandler/dist/node.d.ts +264 -0
- package/node_modules/domhandler/dist/node.d.ts.map +1 -0
- package/node_modules/domhandler/dist/node.js +359 -0
- package/node_modules/domhandler/package.json +65 -0
- package/node_modules/domhandler/readme.md +86 -0
- package/node_modules/domutils/LICENSE +11 -0
- package/node_modules/domutils/dist/feeds.d.ts +71 -0
- package/node_modules/domutils/dist/feeds.d.ts.map +1 -0
- package/node_modules/domutils/dist/feeds.js +180 -0
- package/node_modules/domutils/dist/feeds.js.map +1 -0
- package/node_modules/domutils/dist/helpers.d.ts +59 -0
- package/node_modules/domutils/dist/helpers.d.ts.map +1 -0
- package/node_modules/domutils/dist/helpers.js +136 -0
- package/node_modules/domutils/dist/helpers.js.map +1 -0
- package/node_modules/domutils/dist/index.d.ts +8 -0
- package/node_modules/domutils/dist/index.d.ts.map +1 -0
- package/node_modules/domutils/dist/index.js +8 -0
- package/node_modules/domutils/dist/index.js.map +1 -0
- package/node_modules/domutils/dist/legacy.d.ts +79 -0
- package/node_modules/domutils/dist/legacy.d.ts.map +1 -0
- package/node_modules/domutils/dist/legacy.js +152 -0
- package/node_modules/domutils/dist/legacy.js.map +1 -0
- package/node_modules/domutils/dist/manipulation.d.ts +49 -0
- package/node_modules/domutils/dist/manipulation.d.ts.map +1 -0
- package/node_modules/domutils/dist/manipulation.js +138 -0
- package/node_modules/domutils/dist/manipulation.js.map +1 -0
- package/node_modules/domutils/dist/querying.d.ts +54 -0
- package/node_modules/domutils/dist/querying.d.ts.map +1 -0
- package/node_modules/domutils/dist/querying.js +129 -0
- package/node_modules/domutils/dist/querying.js.map +1 -0
- package/node_modules/domutils/dist/stringify.d.ts +46 -0
- package/node_modules/domutils/dist/stringify.d.ts.map +1 -0
- package/node_modules/domutils/dist/stringify.js +81 -0
- package/node_modules/domutils/dist/stringify.js.map +1 -0
- package/node_modules/domutils/dist/traversal.d.ts +74 -0
- package/node_modules/domutils/dist/traversal.d.ts.map +1 -0
- package/node_modules/domutils/dist/traversal.js +105 -0
- package/node_modules/domutils/dist/traversal.js.map +1 -0
- package/node_modules/domutils/package.json +69 -0
- package/node_modules/domutils/readme.md +25 -0
- package/node_modules/domutils/src/__snapshots__/feeds.spec.ts.snap +103 -0
- package/node_modules/domutils/src/feeds.ts +301 -0
- package/node_modules/domutils/src/helpers.ts +154 -0
- package/node_modules/domutils/src/index.ts +7 -0
- package/node_modules/domutils/src/legacy.ts +224 -0
- package/node_modules/domutils/src/manipulation.ts +156 -0
- package/node_modules/domutils/src/querying.ts +172 -0
- package/node_modules/domutils/src/stringify.ts +90 -0
- package/node_modules/domutils/src/traversal.ts +132 -0
- package/node_modules/entities/LICENSE +11 -0
- package/node_modules/entities/dist/decode-codepoint.d.ts +28 -0
- package/node_modules/entities/dist/decode-codepoint.d.ts.map +1 -0
- package/node_modules/entities/dist/decode-codepoint.js +61 -0
- package/node_modules/entities/dist/decode-codepoint.js.map +1 -0
- package/node_modules/entities/dist/decode.d.ts +227 -0
- package/node_modules/entities/dist/decode.d.ts.map +1 -0
- package/node_modules/entities/dist/decode.js +1132 -0
- package/node_modules/entities/dist/decode.js.map +1 -0
- package/node_modules/entities/dist/encode.d.ts +26 -0
- package/node_modules/entities/dist/encode.d.ts.map +1 -0
- package/node_modules/entities/dist/encode.js +295 -0
- package/node_modules/entities/dist/encode.js.map +1 -0
- package/node_modules/entities/dist/escape.d.ts +60 -0
- package/node_modules/entities/dist/escape.d.ts.map +1 -0
- package/node_modules/entities/dist/escape.js +172 -0
- package/node_modules/entities/dist/escape.js.map +1 -0
- package/node_modules/entities/dist/generated/decode-data-html.d.ts +3 -0
- package/node_modules/entities/dist/generated/decode-data-html.d.ts.map +1 -0
- package/node_modules/entities/dist/generated/decode-data-html.js +5 -0
- package/node_modules/entities/dist/generated/decode-data-html.js.map +1 -0
- package/node_modules/entities/dist/generated/decode-data-xml.d.ts +3 -0
- package/node_modules/entities/dist/generated/decode-data-xml.d.ts.map +1 -0
- package/node_modules/entities/dist/generated/decode-data-xml.js +7 -0
- package/node_modules/entities/dist/generated/decode-data-xml.js.map +1 -0
- package/node_modules/entities/dist/generated/encode-html.d.ts +3 -0
- package/node_modules/entities/dist/generated/encode-html.d.ts.map +1 -0
- package/node_modules/entities/dist/generated/encode-html.js +3 -0
- package/node_modules/entities/dist/generated/encode-html.js.map +1 -0
- package/node_modules/entities/dist/index.d.ts +89 -0
- package/node_modules/entities/dist/index.d.ts.map +1 -0
- package/node_modules/entities/dist/index.js +87 -0
- package/node_modules/entities/dist/index.js.map +1 -0
- package/node_modules/entities/dist/internal/bin-trie-flags.d.ts +48 -0
- package/node_modules/entities/dist/internal/bin-trie-flags.d.ts.map +1 -0
- package/node_modules/entities/dist/internal/bin-trie-flags.js +49 -0
- package/node_modules/entities/dist/internal/bin-trie-flags.js.map +1 -0
- package/node_modules/entities/dist/internal/decode-shared.d.ts +38 -0
- package/node_modules/entities/dist/internal/decode-shared.d.ts.map +1 -0
- package/node_modules/entities/dist/internal/decode-shared.js +195 -0
- package/node_modules/entities/dist/internal/decode-shared.js.map +1 -0
- package/node_modules/entities/package.json +83 -0
- package/node_modules/entities/readme.md +131 -0
- package/node_modules/entities/src/decode-codepoint.ts +69 -0
- package/node_modules/entities/src/decode.ts +1368 -0
- package/node_modules/entities/src/encode.ts +337 -0
- package/node_modules/entities/src/escape.ts +187 -0
- package/node_modules/entities/src/generated/decode-data-html.ts +12 -0
- package/node_modules/entities/src/generated/decode-data-xml.ts +7 -0
- package/node_modules/entities/src/generated/encode-html.ts +3 -0
- package/node_modules/entities/src/index.ts +160 -0
- package/node_modules/entities/src/internal/bin-trie-flags.ts +47 -0
- package/node_modules/entities/src/internal/decode-shared.ts +209 -0
- package/node_modules/escape-string-regexp/index.d.ts +18 -0
- package/node_modules/escape-string-regexp/index.js +13 -0
- package/node_modules/escape-string-regexp/license +9 -0
- package/node_modules/escape-string-regexp/package.json +38 -0
- package/node_modules/escape-string-regexp/readme.md +34 -0
- package/node_modules/express-rate-limit/dist/index.cjs +9 -7
- package/node_modules/express-rate-limit/dist/index.mjs +9 -7
- package/node_modules/express-rate-limit/package.json +9 -9
- package/node_modules/htmlparser2/LICENSE +18 -0
- package/node_modules/htmlparser2/README.md +257 -0
- package/node_modules/htmlparser2/dist/Parser.d.ts +257 -0
- package/node_modules/htmlparser2/dist/Parser.d.ts.map +1 -0
- package/node_modules/htmlparser2/dist/Parser.js +644 -0
- package/node_modules/htmlparser2/dist/Parser.js.map +1 -0
- package/node_modules/htmlparser2/dist/Tokenizer.d.ts +194 -0
- package/node_modules/htmlparser2/dist/Tokenizer.d.ts.map +1 -0
- package/node_modules/htmlparser2/dist/Tokenizer.js +1056 -0
- package/node_modules/htmlparser2/dist/Tokenizer.js.map +1 -0
- package/node_modules/htmlparser2/dist/WebWritableStream.d.ts +24 -0
- package/node_modules/htmlparser2/dist/WebWritableStream.d.ts.map +1 -0
- package/node_modules/htmlparser2/dist/WebWritableStream.js +44 -0
- package/node_modules/htmlparser2/dist/WebWritableStream.js.map +1 -0
- package/node_modules/htmlparser2/dist/WritableStream.d.ts +14 -0
- package/node_modules/htmlparser2/dist/WritableStream.d.ts.map +1 -0
- package/node_modules/htmlparser2/dist/WritableStream.js +32 -0
- package/node_modules/htmlparser2/dist/WritableStream.js.map +1 -0
- package/node_modules/htmlparser2/dist/index.d.ts +34 -0
- package/node_modules/htmlparser2/dist/index.d.ts.map +1 -0
- package/node_modules/htmlparser2/dist/index.js +46 -0
- package/node_modules/htmlparser2/dist/index.js.map +1 -0
- package/node_modules/htmlparser2/package.json +86 -0
- package/node_modules/htmlparser2/src/Parser.ts +823 -0
- package/node_modules/htmlparser2/src/Tokenizer.ts +1150 -0
- package/node_modules/htmlparser2/src/WebWritableStream.ts +47 -0
- package/node_modules/htmlparser2/src/WritableStream.ts +42 -0
- package/node_modules/htmlparser2/src/index.ts +86 -0
- package/node_modules/launder/.travis.yml +4 -0
- package/node_modules/launder/CHANGELOG.md +80 -0
- package/node_modules/launder/LICENSE.md +7 -0
- package/node_modules/launder/README.md +177 -0
- package/node_modules/launder/eslint.config.js +6 -0
- package/node_modules/launder/index.js +538 -0
- package/node_modules/launder/logos/logo-box-madefor.png +0 -0
- package/node_modules/launder/package.json +38 -0
- package/node_modules/launder/test/test.js +775 -0
- package/node_modules/nanoid/LICENSE +20 -0
- package/node_modules/nanoid/README.md +39 -0
- package/node_modules/nanoid/async/index.browser.cjs +43 -0
- package/node_modules/nanoid/async/index.browser.js +43 -0
- package/node_modules/nanoid/async/index.cjs +47 -0
- package/node_modules/nanoid/async/index.d.cts +56 -0
- package/node_modules/nanoid/async/index.d.ts +56 -0
- package/node_modules/nanoid/async/index.js +47 -0
- package/node_modules/nanoid/async/index.native.js +38 -0
- package/node_modules/nanoid/async/package.json +12 -0
- package/node_modules/nanoid/bin/nanoid.cjs +55 -0
- package/node_modules/nanoid/index.browser.cjs +45 -0
- package/node_modules/nanoid/index.browser.js +45 -0
- package/node_modules/nanoid/index.cjs +63 -0
- package/node_modules/nanoid/index.d.cts +91 -0
- package/node_modules/nanoid/index.d.ts +91 -0
- package/node_modules/nanoid/index.js +63 -0
- package/node_modules/nanoid/nanoid.js +1 -0
- package/node_modules/nanoid/non-secure/index.cjs +24 -0
- package/node_modules/nanoid/non-secure/index.d.ts +33 -0
- package/node_modules/nanoid/non-secure/index.js +24 -0
- package/node_modules/nanoid/non-secure/package.json +6 -0
- package/node_modules/nanoid/package.json +89 -0
- package/node_modules/nanoid/url-alphabet/index.cjs +4 -0
- package/node_modules/nanoid/url-alphabet/index.js +4 -0
- package/node_modules/nanoid/url-alphabet/package.json +6 -0
- package/node_modules/parse-srcset/.jscs.json +83 -0
- package/node_modules/parse-srcset/.jshintrc +15 -0
- package/node_modules/parse-srcset/.npmignore +8 -0
- package/node_modules/parse-srcset/LICENSE +22 -0
- package/node_modules/parse-srcset/README.md +19 -0
- package/node_modules/parse-srcset/package.json +25 -0
- package/node_modules/parse-srcset/src/parse-srcset.js +330 -0
- package/node_modules/parse-srcset/tests/he.js +329 -0
- package/node_modules/parse-srcset/tests/intern.js +74 -0
- package/node_modules/parse-srcset/tests/unit/ps.js +345 -0
- package/node_modules/picocolors/LICENSE +15 -0
- package/node_modules/picocolors/README.md +21 -0
- package/node_modules/picocolors/package.json +25 -0
- package/node_modules/picocolors/picocolors.browser.js +4 -0
- package/node_modules/picocolors/picocolors.d.ts +5 -0
- package/node_modules/picocolors/picocolors.js +75 -0
- package/node_modules/picocolors/types.d.ts +51 -0
- package/node_modules/postcss/LICENSE +20 -0
- package/node_modules/postcss/README.md +27 -0
- package/node_modules/postcss/lib/at-rule.d.ts +139 -0
- package/node_modules/postcss/lib/at-rule.js +25 -0
- package/node_modules/postcss/lib/comment.d.ts +67 -0
- package/node_modules/postcss/lib/comment.js +13 -0
- package/node_modules/postcss/lib/container.d.ts +474 -0
- package/node_modules/postcss/lib/container.js +494 -0
- package/node_modules/postcss/lib/css-syntax-error.d.ts +247 -0
- package/node_modules/postcss/lib/css-syntax-error.js +133 -0
- package/node_modules/postcss/lib/declaration.d.ts +150 -0
- package/node_modules/postcss/lib/declaration.js +24 -0
- package/node_modules/postcss/lib/document.d.ts +68 -0
- package/node_modules/postcss/lib/document.js +33 -0
- package/node_modules/postcss/lib/fromJSON.d.ts +9 -0
- package/node_modules/postcss/lib/fromJSON.js +107 -0
- package/node_modules/postcss/lib/input.d.ts +226 -0
- package/node_modules/postcss/lib/input.js +282 -0
- package/node_modules/postcss/lib/lazy-result.d.ts +189 -0
- package/node_modules/postcss/lib/lazy-result.js +626 -0
- package/node_modules/postcss/lib/list.d.ts +60 -0
- package/node_modules/postcss/lib/list.js +79 -0
- package/node_modules/postcss/lib/map-generator.js +383 -0
- package/node_modules/postcss/lib/no-work-result.d.ts +45 -0
- package/node_modules/postcss/lib/no-work-result.js +137 -0
- package/node_modules/postcss/lib/node.d.ts +555 -0
- package/node_modules/postcss/lib/node.js +494 -0
- package/node_modules/postcss/lib/parse.d.ts +9 -0
- package/node_modules/postcss/lib/parse.js +42 -0
- package/node_modules/postcss/lib/parser.js +626 -0
- package/node_modules/postcss/lib/postcss.d.mts +66 -0
- package/node_modules/postcss/lib/postcss.d.ts +464 -0
- package/node_modules/postcss/lib/postcss.js +101 -0
- package/node_modules/postcss/lib/postcss.mjs +30 -0
- package/node_modules/postcss/lib/previous-map.d.ts +80 -0
- package/node_modules/postcss/lib/previous-map.js +176 -0
- package/node_modules/postcss/lib/processor.d.ts +114 -0
- package/node_modules/postcss/lib/processor.js +67 -0
- package/node_modules/postcss/lib/result.d.ts +204 -0
- package/node_modules/postcss/lib/result.js +42 -0
- package/node_modules/postcss/lib/root.d.ts +86 -0
- package/node_modules/postcss/lib/root.js +76 -0
- package/node_modules/postcss/lib/rule.d.ts +126 -0
- package/node_modules/postcss/lib/rule.js +27 -0
- package/node_modules/postcss/lib/stringifier.d.ts +45 -0
- package/node_modules/postcss/lib/stringifier.js +457 -0
- package/node_modules/postcss/lib/stringify.d.ts +9 -0
- package/node_modules/postcss/lib/stringify.js +69 -0
- package/node_modules/postcss/lib/symbols.js +5 -0
- package/node_modules/postcss/lib/terminal-highlight.js +70 -0
- package/node_modules/postcss/lib/tokenize.js +270 -0
- package/node_modules/postcss/lib/warn-once.js +13 -0
- package/node_modules/postcss/lib/warning.d.ts +146 -0
- package/node_modules/postcss/lib/warning.js +47 -0
- package/node_modules/postcss/package.json +88 -0
- package/node_modules/sanitize-html/LICENSE +7 -0
- package/node_modules/sanitize-html/README.md +877 -0
- package/node_modules/sanitize-html/index.js +1255 -0
- package/node_modules/sanitize-html/package.json +49 -0
- package/node_modules/source-map-js/LICENSE +28 -0
- package/node_modules/source-map-js/README.md +765 -0
- package/node_modules/source-map-js/lib/array-set.js +121 -0
- package/node_modules/source-map-js/lib/base64-vlq.js +140 -0
- package/node_modules/source-map-js/lib/base64.js +67 -0
- package/node_modules/source-map-js/lib/binary-search.js +111 -0
- package/node_modules/source-map-js/lib/mapping-list.js +79 -0
- package/node_modules/source-map-js/lib/quick-sort.js +141 -0
- package/node_modules/source-map-js/lib/source-map-consumer.d.ts +1 -0
- package/node_modules/source-map-js/lib/source-map-consumer.js +1236 -0
- package/node_modules/source-map-js/lib/source-map-generator.d.ts +1 -0
- package/node_modules/source-map-js/lib/source-map-generator.js +446 -0
- package/node_modules/source-map-js/lib/source-node.d.ts +1 -0
- package/node_modules/source-map-js/lib/source-node.js +420 -0
- package/node_modules/source-map-js/lib/util.js +594 -0
- package/node_modules/source-map-js/package.json +71 -0
- package/node_modules/source-map-js/source-map.d.ts +104 -0
- package/node_modules/source-map-js/source-map.js +8 -0
- package/package.json +5 -1
- package/dist/ui/assets/index-BKgX8_Zh.js +0 -111
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CASE_TIME_UNKNOWN_REASON,
|
|
4
|
+
METRIC_LABELS,
|
|
5
|
+
fingerprintOf,
|
|
6
|
+
formatActionLabel,
|
|
7
|
+
formatCurrency,
|
|
8
|
+
formatDateTime,
|
|
9
|
+
formatDuration,
|
|
10
|
+
formatMetricValue,
|
|
11
|
+
parseCaseAttrs,
|
|
12
|
+
parseElements,
|
|
13
|
+
parseTimestampSeconds,
|
|
14
|
+
profileShortLabel,
|
|
15
|
+
readPairing,
|
|
16
|
+
resolveApprovalElement,
|
|
17
|
+
resolveCaseElement,
|
|
18
|
+
resolveMandateElement,
|
|
19
|
+
resolveTicketElement
|
|
20
|
+
} from "./chunk-QOKPECVY.mjs";
|
|
2
21
|
|
|
3
22
|
// bin/http.ts
|
|
4
23
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -112,42 +131,13 @@ function clientVersionHeaders() {
|
|
|
112
131
|
};
|
|
113
132
|
}
|
|
114
133
|
|
|
115
|
-
// src/lib/as-pairing.ts
|
|
116
|
-
import { createHash } from "crypto";
|
|
117
|
-
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
|
|
118
|
-
import { dirname, join as join2 } from "path";
|
|
119
|
-
function pairingPath(dataDir2) {
|
|
120
|
-
return join2(dataDir2, "as-pairing.json");
|
|
121
|
-
}
|
|
122
|
-
function readPairing(dataDir2) {
|
|
123
|
-
const path = pairingPath(dataDir2);
|
|
124
|
-
if (!existsSync2(path)) return null;
|
|
125
|
-
try {
|
|
126
|
-
const data = JSON.parse(readFileSync2(path, "utf-8"));
|
|
127
|
-
if (typeof data.asUrl !== "string" || typeof data.publicKeyHex !== "string") return null;
|
|
128
|
-
return {
|
|
129
|
-
asUrl: data.asUrl,
|
|
130
|
-
publicKeyHex: data.publicKeyHex,
|
|
131
|
-
pairedAt: data.pairedAt ?? "",
|
|
132
|
-
...typeof data.tlsSpkiPinHex === "string" ? { tlsSpkiPinHex: data.tlsSpkiPinHex } : {}
|
|
133
|
-
};
|
|
134
|
-
} catch {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function fingerprintOf(publicKeyHex) {
|
|
139
|
-
const raw = Buffer.from(publicKeyHex, "hex");
|
|
140
|
-
const digest = createHash("sha256").update(raw).digest("hex").toUpperCase();
|
|
141
|
-
return digest.match(/.{1,4}/g)?.join(":") ?? digest;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
134
|
// src/lib/as-config.ts
|
|
145
|
-
import { existsSync as
|
|
146
|
-
import { dirname
|
|
135
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
136
|
+
import { dirname, join as join3 } from "path";
|
|
147
137
|
|
|
148
138
|
// src/lib/policy.ts
|
|
149
|
-
import { existsSync as
|
|
150
|
-
import { join as
|
|
139
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
140
|
+
import { join as join2 } from "path";
|
|
151
141
|
import { spawnSync } from "child_process";
|
|
152
142
|
var POLICY_KEYS = ["AsUrl", "CaFile", "PinTls", "Proxy", "NoProxy", "Simulation", "InstallMethod"];
|
|
153
143
|
var DEFAULT_REGISTRY_BASE_KEY = "SOFTWARE\\Policies\\Suveren\\Gateway";
|
|
@@ -182,7 +172,7 @@ function validateAsUrl(candidate) {
|
|
|
182
172
|
function validateCaFile(candidate) {
|
|
183
173
|
const trimmed = candidate.trim();
|
|
184
174
|
if (!trimmed) return { ok: false, error: "The CA file path is empty." };
|
|
185
|
-
if (!
|
|
175
|
+
if (!existsSync2(trimmed)) return { ok: false, error: `No file at "${trimmed}".` };
|
|
186
176
|
return { ok: true, path: trimmed };
|
|
187
177
|
}
|
|
188
178
|
function validateProxyUrl(candidate) {
|
|
@@ -248,7 +238,7 @@ function policyFilePath() {
|
|
|
248
238
|
if (process.env.SUVEREN_POLICY_FILE) return process.env.SUVEREN_POLICY_FILE;
|
|
249
239
|
if (process.platform === "win32") {
|
|
250
240
|
const programData = process.env.ProgramData || process.env.PROGRAMDATA || "C:\\ProgramData";
|
|
251
|
-
return
|
|
241
|
+
return join2(programData, "Suveren", "gateway-policy.json");
|
|
252
242
|
}
|
|
253
243
|
if (process.platform === "darwin") {
|
|
254
244
|
return "/Library/Application Support/Suveren/gateway-policy.json";
|
|
@@ -258,10 +248,10 @@ function policyFilePath() {
|
|
|
258
248
|
function readPolicyFile() {
|
|
259
249
|
const path = policyFilePath();
|
|
260
250
|
const source = `the policy file (${path})`;
|
|
261
|
-
if (!
|
|
251
|
+
if (!existsSync2(path)) return { values: {}, source };
|
|
262
252
|
let data;
|
|
263
253
|
try {
|
|
264
|
-
data = JSON.parse(
|
|
254
|
+
data = JSON.parse(readFileSync2(path, "utf8"));
|
|
265
255
|
} catch (err) {
|
|
266
256
|
throw new Error(`Could not parse policy file ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
267
257
|
}
|
|
@@ -365,14 +355,14 @@ function readPolicy() {
|
|
|
365
355
|
// src/lib/as-config.ts
|
|
366
356
|
var DEFAULT_AS_URL = "https://www.suveren.ai";
|
|
367
357
|
function configPath(dataDir2) {
|
|
368
|
-
return
|
|
358
|
+
return join3(dataDir2, "config.json");
|
|
369
359
|
}
|
|
370
360
|
function readAsConfigStrict(dataDir2) {
|
|
371
361
|
const path = configPath(dataDir2);
|
|
372
|
-
if (!
|
|
362
|
+
if (!existsSync3(path)) return {};
|
|
373
363
|
let data;
|
|
374
364
|
try {
|
|
375
|
-
data = JSON.parse(
|
|
365
|
+
data = JSON.parse(readFileSync3(path, "utf-8"));
|
|
376
366
|
} catch (err) {
|
|
377
367
|
throw new Error(
|
|
378
368
|
`Could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -387,9 +377,9 @@ function readAsConfigStrict(dataDir2) {
|
|
|
387
377
|
}
|
|
388
378
|
function readAsConfig(dataDir2) {
|
|
389
379
|
const path = configPath(dataDir2);
|
|
390
|
-
if (!
|
|
380
|
+
if (!existsSync3(path)) return {};
|
|
391
381
|
try {
|
|
392
|
-
const data = JSON.parse(
|
|
382
|
+
const data = JSON.parse(readFileSync3(path, "utf-8"));
|
|
393
383
|
const out = {};
|
|
394
384
|
if (typeof data.asUrl === "string") out.asUrl = data.asUrl;
|
|
395
385
|
if (typeof data.caFile === "string") out.caFile = data.caFile;
|
|
@@ -461,10 +451,10 @@ function validatePinTlsForUrl(pinTls, asUrl) {
|
|
|
461
451
|
}
|
|
462
452
|
|
|
463
453
|
// src/lib/as-tls-pin.ts
|
|
464
|
-
import { createHash
|
|
454
|
+
import { createHash } from "crypto";
|
|
465
455
|
import { checkServerIdentity as defaultCheckServerIdentity, rootCertificates } from "tls";
|
|
466
456
|
import { Agent as HttpsAgent2 } from "https";
|
|
467
|
-
import { readFileSync as
|
|
457
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
468
458
|
import { Agent, ProxyAgent, fetch as undiciFetch } from "undici";
|
|
469
459
|
|
|
470
460
|
// src/lib/proxy-env.ts
|
|
@@ -514,7 +504,7 @@ function effectiveCa() {
|
|
|
514
504
|
const path = process.env.NODE_EXTRA_CA_CERTS;
|
|
515
505
|
if (!path) return void 0;
|
|
516
506
|
try {
|
|
517
|
-
return [...rootCertificates,
|
|
507
|
+
return [...rootCertificates, readFileSync4(path, "utf-8")];
|
|
518
508
|
} catch {
|
|
519
509
|
return void 0;
|
|
520
510
|
}
|
|
@@ -542,7 +532,7 @@ function spkiSha256Hex(cert) {
|
|
|
542
532
|
if (!cert.pubkey) {
|
|
543
533
|
throw new Error("peer certificate carries no pubkey (expected the detailed certificate object)");
|
|
544
534
|
}
|
|
545
|
-
return
|
|
535
|
+
return createHash("sha256").update(cert.pubkey).digest("hex");
|
|
546
536
|
}
|
|
547
537
|
function formatPinFingerprint(hex) {
|
|
548
538
|
const upper = hex.toUpperCase();
|
|
@@ -1148,18 +1138,18 @@ var AttestationCache = class {
|
|
|
1148
1138
|
};
|
|
1149
1139
|
|
|
1150
1140
|
// src/lib/gate-store.ts
|
|
1151
|
-
import { mkdirSync as
|
|
1141
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync4, unlinkSync } from "fs";
|
|
1152
1142
|
import { homedir } from "os";
|
|
1153
|
-
import { dirname as
|
|
1143
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
1154
1144
|
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
1155
|
-
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
1145
|
+
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ?? join4(homedir(), ".suveren");
|
|
1156
1146
|
var GateStore = class {
|
|
1157
1147
|
entries = /* @__PURE__ */ new Map();
|
|
1158
1148
|
baseDir;
|
|
1159
1149
|
vaultKey = null;
|
|
1160
1150
|
constructor(filePath) {
|
|
1161
1151
|
if (filePath && filePath.endsWith(".json")) {
|
|
1162
|
-
this.baseDir =
|
|
1152
|
+
this.baseDir = dirname2(filePath);
|
|
1163
1153
|
} else {
|
|
1164
1154
|
this.baseDir = filePath ?? DEFAULT_DIR;
|
|
1165
1155
|
}
|
|
@@ -1170,13 +1160,13 @@ var GateStore = class {
|
|
|
1170
1160
|
*/
|
|
1171
1161
|
setVaultKey(key) {
|
|
1172
1162
|
this.vaultKey = key;
|
|
1173
|
-
if (
|
|
1163
|
+
if (existsSync4(this.encryptedFilePath)) {
|
|
1174
1164
|
this.loadEncrypted();
|
|
1175
1165
|
} else if (this.entries.size > 0) {
|
|
1176
1166
|
this.persistEncrypted();
|
|
1177
|
-
if (
|
|
1167
|
+
if (existsSync4(this.plaintextFilePath)) {
|
|
1178
1168
|
try {
|
|
1179
|
-
|
|
1169
|
+
unlinkSync(this.plaintextFilePath);
|
|
1180
1170
|
} catch {
|
|
1181
1171
|
}
|
|
1182
1172
|
}
|
|
@@ -1212,11 +1202,11 @@ var GateStore = class {
|
|
|
1212
1202
|
clearAll() {
|
|
1213
1203
|
this.entries = /* @__PURE__ */ new Map();
|
|
1214
1204
|
try {
|
|
1215
|
-
if (
|
|
1205
|
+
if (existsSync4(this.plaintextFilePath)) unlinkSync(this.plaintextFilePath);
|
|
1216
1206
|
} catch {
|
|
1217
1207
|
}
|
|
1218
1208
|
try {
|
|
1219
|
-
if (
|
|
1209
|
+
if (existsSync4(this.encryptedFilePath)) unlinkSync(this.encryptedFilePath);
|
|
1220
1210
|
} catch {
|
|
1221
1211
|
}
|
|
1222
1212
|
}
|
|
@@ -1249,19 +1239,19 @@ var GateStore = class {
|
|
|
1249
1239
|
}
|
|
1250
1240
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
1251
1241
|
get plaintextFilePath() {
|
|
1252
|
-
return
|
|
1242
|
+
return join4(this.baseDir, "gates.json");
|
|
1253
1243
|
}
|
|
1254
1244
|
get encryptedFilePath() {
|
|
1255
|
-
return
|
|
1245
|
+
return join4(this.baseDir, "gates.enc.json");
|
|
1256
1246
|
}
|
|
1257
1247
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
1258
1248
|
loadPlaintext() {
|
|
1259
|
-
if (!
|
|
1260
|
-
|
|
1249
|
+
if (!existsSync4(this.plaintextFilePath)) {
|
|
1250
|
+
mkdirSync2(this.baseDir, { recursive: true });
|
|
1261
1251
|
return;
|
|
1262
1252
|
}
|
|
1263
1253
|
try {
|
|
1264
|
-
const raw =
|
|
1254
|
+
const raw = readFileSync5(this.plaintextFilePath, "utf-8");
|
|
1265
1255
|
const data = JSON.parse(raw);
|
|
1266
1256
|
this.entries = new Map(Object.entries(data.entries));
|
|
1267
1257
|
} catch {
|
|
@@ -1270,9 +1260,9 @@ var GateStore = class {
|
|
|
1270
1260
|
}
|
|
1271
1261
|
}
|
|
1272
1262
|
loadEncrypted() {
|
|
1273
|
-
if (!
|
|
1263
|
+
if (!existsSync4(this.encryptedFilePath)) return;
|
|
1274
1264
|
try {
|
|
1275
|
-
const raw =
|
|
1265
|
+
const raw = readFileSync5(this.encryptedFilePath, "utf-8");
|
|
1276
1266
|
const data = JSON.parse(raw);
|
|
1277
1267
|
this.entries = /* @__PURE__ */ new Map();
|
|
1278
1268
|
for (const [key, blob] of Object.entries(data.entries)) {
|
|
@@ -1296,8 +1286,8 @@ var GateStore = class {
|
|
|
1296
1286
|
version: 1,
|
|
1297
1287
|
entries: Object.fromEntries(this.entries)
|
|
1298
1288
|
};
|
|
1299
|
-
|
|
1300
|
-
|
|
1289
|
+
mkdirSync2(this.baseDir, { recursive: true, mode: 448 });
|
|
1290
|
+
writeFileSync2(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1301
1291
|
}
|
|
1302
1292
|
persistEncrypted() {
|
|
1303
1293
|
const data = {
|
|
@@ -1307,17 +1297,17 @@ var GateStore = class {
|
|
|
1307
1297
|
for (const [key, entry] of this.entries) {
|
|
1308
1298
|
data.entries[key] = this.encrypt(JSON.stringify(entry));
|
|
1309
1299
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1300
|
+
mkdirSync2(this.baseDir, { recursive: true, mode: 448 });
|
|
1301
|
+
writeFileSync2(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1312
1302
|
}
|
|
1313
1303
|
};
|
|
1314
1304
|
|
|
1315
1305
|
// src/lib/execution-log.ts
|
|
1316
|
-
import { mkdirSync as
|
|
1306
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
1317
1307
|
import { homedir as homedir2 } from "os";
|
|
1318
|
-
import { dirname as
|
|
1308
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
1319
1309
|
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
|
|
1320
|
-
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
1310
|
+
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ?? join5(homedir2(), ".suveren");
|
|
1321
1311
|
var MAX_AGE_SECONDS = 31 * 24 * 60 * 60;
|
|
1322
1312
|
var ExecutionLog = class {
|
|
1323
1313
|
entries = [];
|
|
@@ -1325,7 +1315,7 @@ var ExecutionLog = class {
|
|
|
1325
1315
|
vaultKey = null;
|
|
1326
1316
|
constructor(filePath) {
|
|
1327
1317
|
if (filePath && filePath.endsWith(".json")) {
|
|
1328
|
-
this.baseDir =
|
|
1318
|
+
this.baseDir = dirname3(filePath);
|
|
1329
1319
|
} else {
|
|
1330
1320
|
this.baseDir = filePath ?? DEFAULT_DIR2;
|
|
1331
1321
|
}
|
|
@@ -1336,13 +1326,13 @@ var ExecutionLog = class {
|
|
|
1336
1326
|
*/
|
|
1337
1327
|
setVaultKey(key) {
|
|
1338
1328
|
this.vaultKey = key;
|
|
1339
|
-
if (
|
|
1329
|
+
if (existsSync5(this.encryptedFilePath)) {
|
|
1340
1330
|
this.loadEncrypted();
|
|
1341
1331
|
} else if (this.entries.length > 0) {
|
|
1342
1332
|
this.persistEncrypted();
|
|
1343
|
-
if (
|
|
1333
|
+
if (existsSync5(this.plaintextFilePath)) {
|
|
1344
1334
|
try {
|
|
1345
|
-
|
|
1335
|
+
unlinkSync2(this.plaintextFilePath);
|
|
1346
1336
|
} catch {
|
|
1347
1337
|
}
|
|
1348
1338
|
}
|
|
@@ -1444,19 +1434,19 @@ var ExecutionLog = class {
|
|
|
1444
1434
|
}
|
|
1445
1435
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
1446
1436
|
get plaintextFilePath() {
|
|
1447
|
-
return
|
|
1437
|
+
return join5(this.baseDir, "execution-log.json");
|
|
1448
1438
|
}
|
|
1449
1439
|
get encryptedFilePath() {
|
|
1450
|
-
return
|
|
1440
|
+
return join5(this.baseDir, "execution-log.enc.json");
|
|
1451
1441
|
}
|
|
1452
1442
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
1453
1443
|
loadPlaintext() {
|
|
1454
|
-
if (!
|
|
1455
|
-
|
|
1444
|
+
if (!existsSync5(this.plaintextFilePath)) {
|
|
1445
|
+
mkdirSync3(this.baseDir, { recursive: true });
|
|
1456
1446
|
return;
|
|
1457
1447
|
}
|
|
1458
1448
|
try {
|
|
1459
|
-
const raw =
|
|
1449
|
+
const raw = readFileSync6(this.plaintextFilePath, "utf-8");
|
|
1460
1450
|
const data = JSON.parse(raw);
|
|
1461
1451
|
this.entries = data.entries;
|
|
1462
1452
|
this.prune();
|
|
@@ -1466,9 +1456,9 @@ var ExecutionLog = class {
|
|
|
1466
1456
|
}
|
|
1467
1457
|
}
|
|
1468
1458
|
loadEncrypted() {
|
|
1469
|
-
if (!
|
|
1459
|
+
if (!existsSync5(this.encryptedFilePath)) return;
|
|
1470
1460
|
try {
|
|
1471
|
-
const raw =
|
|
1461
|
+
const raw = readFileSync6(this.encryptedFilePath, "utf-8");
|
|
1472
1462
|
const data = JSON.parse(raw);
|
|
1473
1463
|
const decrypted = this.decrypt(data.blob);
|
|
1474
1464
|
const logFile = JSON.parse(decrypted);
|
|
@@ -1488,8 +1478,8 @@ var ExecutionLog = class {
|
|
|
1488
1478
|
}
|
|
1489
1479
|
persistPlaintext() {
|
|
1490
1480
|
const data = { version: 1, entries: this.entries };
|
|
1491
|
-
|
|
1492
|
-
|
|
1481
|
+
mkdirSync3(this.baseDir, { recursive: true, mode: 448 });
|
|
1482
|
+
writeFileSync3(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1493
1483
|
}
|
|
1494
1484
|
persistEncrypted() {
|
|
1495
1485
|
const logFile = { version: 1, entries: this.entries };
|
|
@@ -1497,8 +1487,8 @@ var ExecutionLog = class {
|
|
|
1497
1487
|
version: 1,
|
|
1498
1488
|
blob: this.encrypt(JSON.stringify(logFile))
|
|
1499
1489
|
};
|
|
1500
|
-
|
|
1501
|
-
|
|
1490
|
+
mkdirSync3(this.baseDir, { recursive: true, mode: 448 });
|
|
1491
|
+
writeFileSync3(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1502
1492
|
}
|
|
1503
1493
|
};
|
|
1504
1494
|
function windowCutoff(window, now) {
|
|
@@ -1515,11 +1505,11 @@ function windowCutoff(window, now) {
|
|
|
1515
1505
|
}
|
|
1516
1506
|
|
|
1517
1507
|
// src/lib/denial-log.ts
|
|
1518
|
-
import { mkdirSync as
|
|
1508
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync6, unlinkSync as unlinkSync3 } from "fs";
|
|
1519
1509
|
import { homedir as homedir3 } from "os";
|
|
1520
|
-
import { dirname as
|
|
1510
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
1521
1511
|
import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, randomBytes as randomBytes3 } from "crypto";
|
|
1522
|
-
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ??
|
|
1512
|
+
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ?? join6(homedir3(), ".suveren");
|
|
1523
1513
|
var MAX_RECORDS = 200;
|
|
1524
1514
|
var MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1525
1515
|
var DenialLog = class {
|
|
@@ -1527,18 +1517,18 @@ var DenialLog = class {
|
|
|
1527
1517
|
baseDir;
|
|
1528
1518
|
vaultKey = null;
|
|
1529
1519
|
constructor(filePath) {
|
|
1530
|
-
this.baseDir = filePath && filePath.endsWith(".json") ?
|
|
1520
|
+
this.baseDir = filePath && filePath.endsWith(".json") ? dirname4(filePath) : filePath ?? DEFAULT_DIR3;
|
|
1531
1521
|
this.loadPlaintext();
|
|
1532
1522
|
}
|
|
1533
1523
|
setVaultKey(key) {
|
|
1534
1524
|
this.vaultKey = key;
|
|
1535
|
-
if (
|
|
1525
|
+
if (existsSync6(this.encryptedFilePath)) {
|
|
1536
1526
|
this.loadEncrypted();
|
|
1537
1527
|
} else if (this.records.length > 0) {
|
|
1538
1528
|
this.persistEncrypted();
|
|
1539
|
-
if (
|
|
1529
|
+
if (existsSync6(this.plaintextFilePath)) {
|
|
1540
1530
|
try {
|
|
1541
|
-
|
|
1531
|
+
unlinkSync3(this.plaintextFilePath);
|
|
1542
1532
|
} catch {
|
|
1543
1533
|
}
|
|
1544
1534
|
}
|
|
@@ -1586,18 +1576,18 @@ var DenialLog = class {
|
|
|
1586
1576
|
return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]).toString("utf8");
|
|
1587
1577
|
}
|
|
1588
1578
|
get plaintextFilePath() {
|
|
1589
|
-
return
|
|
1579
|
+
return join6(this.baseDir, "denials.json");
|
|
1590
1580
|
}
|
|
1591
1581
|
get encryptedFilePath() {
|
|
1592
|
-
return
|
|
1582
|
+
return join6(this.baseDir, "denials.enc.json");
|
|
1593
1583
|
}
|
|
1594
1584
|
loadPlaintext() {
|
|
1595
|
-
if (!
|
|
1596
|
-
|
|
1585
|
+
if (!existsSync6(this.plaintextFilePath)) {
|
|
1586
|
+
mkdirSync4(this.baseDir, { recursive: true });
|
|
1597
1587
|
return;
|
|
1598
1588
|
}
|
|
1599
1589
|
try {
|
|
1600
|
-
const data = JSON.parse(
|
|
1590
|
+
const data = JSON.parse(readFileSync7(this.plaintextFilePath, "utf-8"));
|
|
1601
1591
|
this.records = data.records ?? [];
|
|
1602
1592
|
} catch {
|
|
1603
1593
|
console.error(`[DenialLog] Could not parse ${this.plaintextFilePath}, starting fresh`);
|
|
@@ -1605,9 +1595,9 @@ var DenialLog = class {
|
|
|
1605
1595
|
}
|
|
1606
1596
|
}
|
|
1607
1597
|
loadEncrypted() {
|
|
1608
|
-
if (!
|
|
1598
|
+
if (!existsSync6(this.encryptedFilePath)) return;
|
|
1609
1599
|
try {
|
|
1610
|
-
const data = JSON.parse(
|
|
1600
|
+
const data = JSON.parse(readFileSync7(this.encryptedFilePath, "utf-8"));
|
|
1611
1601
|
const logFile = JSON.parse(this.decrypt(data.blob));
|
|
1612
1602
|
this.records = logFile.records ?? [];
|
|
1613
1603
|
} catch (err) {
|
|
@@ -1621,23 +1611,23 @@ var DenialLog = class {
|
|
|
1621
1611
|
}
|
|
1622
1612
|
persistPlaintext() {
|
|
1623
1613
|
const data = { version: 1, records: this.records };
|
|
1624
|
-
|
|
1625
|
-
|
|
1614
|
+
mkdirSync4(this.baseDir, { recursive: true, mode: 448 });
|
|
1615
|
+
writeFileSync4(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1626
1616
|
}
|
|
1627
1617
|
persistEncrypted() {
|
|
1628
1618
|
const logFile = { version: 1, records: this.records };
|
|
1629
1619
|
const data = { version: 1, blob: this.encrypt(JSON.stringify(logFile)) };
|
|
1630
|
-
|
|
1631
|
-
|
|
1620
|
+
mkdirSync4(this.baseDir, { recursive: true, mode: 448 });
|
|
1621
|
+
writeFileSync4(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1632
1622
|
}
|
|
1633
1623
|
};
|
|
1634
1624
|
|
|
1635
1625
|
// src/lib/receipt-archive.ts
|
|
1636
|
-
import { mkdirSync as
|
|
1626
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync7, unlinkSync as unlinkSync4 } from "fs";
|
|
1637
1627
|
import { homedir as homedir4 } from "os";
|
|
1638
|
-
import { dirname as
|
|
1628
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
1639
1629
|
import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes4 } from "crypto";
|
|
1640
|
-
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ??
|
|
1630
|
+
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ?? join7(homedir4(), ".suveren");
|
|
1641
1631
|
function stripPreviews(proposal) {
|
|
1642
1632
|
const args = proposal.toolArgs;
|
|
1643
1633
|
if (!args || typeof args !== "object") return proposal;
|
|
@@ -1651,7 +1641,7 @@ var ReceiptArchive = class {
|
|
|
1651
1641
|
vaultKey = null;
|
|
1652
1642
|
constructor(filePath) {
|
|
1653
1643
|
if (filePath && filePath.endsWith(".json")) {
|
|
1654
|
-
this.baseDir =
|
|
1644
|
+
this.baseDir = dirname5(filePath);
|
|
1655
1645
|
} else {
|
|
1656
1646
|
this.baseDir = filePath ?? DEFAULT_DIR4;
|
|
1657
1647
|
}
|
|
@@ -1662,13 +1652,13 @@ var ReceiptArchive = class {
|
|
|
1662
1652
|
*/
|
|
1663
1653
|
setVaultKey(key) {
|
|
1664
1654
|
this.vaultKey = key;
|
|
1665
|
-
if (
|
|
1655
|
+
if (existsSync7(this.encryptedFilePath)) {
|
|
1666
1656
|
this.loadEncrypted();
|
|
1667
1657
|
} else if (this.data.receipts.length > 0 || Object.keys(this.data.authorizations).length > 0) {
|
|
1668
1658
|
this.persistEncrypted();
|
|
1669
|
-
if (
|
|
1659
|
+
if (existsSync7(this.plaintextFilePath)) {
|
|
1670
1660
|
try {
|
|
1671
|
-
|
|
1661
|
+
unlinkSync4(this.plaintextFilePath);
|
|
1672
1662
|
} catch {
|
|
1673
1663
|
}
|
|
1674
1664
|
}
|
|
@@ -1680,7 +1670,7 @@ var ReceiptArchive = class {
|
|
|
1680
1670
|
* this state — that would report a hollow success over unreadable evidence.
|
|
1681
1671
|
*/
|
|
1682
1672
|
isLocked() {
|
|
1683
|
-
return this.vaultKey === null &&
|
|
1673
|
+
return this.vaultKey === null && existsSync7(this.encryptedFilePath);
|
|
1684
1674
|
}
|
|
1685
1675
|
/**
|
|
1686
1676
|
* Archive a signed receipt (and the grant it was issued under).
|
|
@@ -1688,8 +1678,8 @@ var ReceiptArchive = class {
|
|
|
1688
1678
|
*/
|
|
1689
1679
|
record(entry, now = Math.floor(Date.now() / 1e3)) {
|
|
1690
1680
|
let changed = false;
|
|
1691
|
-
const
|
|
1692
|
-
const alreadyArchived =
|
|
1681
|
+
const receiptId2 = typeof entry.receipt?.id === "string" ? entry.receipt.id : void 0;
|
|
1682
|
+
const alreadyArchived = receiptId2 !== void 0 && this.data.receipts.some((r) => r.receipt.id === receiptId2);
|
|
1693
1683
|
if (!alreadyArchived) {
|
|
1694
1684
|
this.data.receipts.push({
|
|
1695
1685
|
archivedAt: now,
|
|
@@ -1781,19 +1771,19 @@ var ReceiptArchive = class {
|
|
|
1781
1771
|
}
|
|
1782
1772
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
1783
1773
|
get plaintextFilePath() {
|
|
1784
|
-
return
|
|
1774
|
+
return join7(this.baseDir, "receipt-archive.json");
|
|
1785
1775
|
}
|
|
1786
1776
|
get encryptedFilePath() {
|
|
1787
|
-
return
|
|
1777
|
+
return join7(this.baseDir, "receipt-archive.enc.json");
|
|
1788
1778
|
}
|
|
1789
1779
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
1790
1780
|
loadPlaintext() {
|
|
1791
|
-
if (!
|
|
1792
|
-
|
|
1781
|
+
if (!existsSync7(this.plaintextFilePath)) {
|
|
1782
|
+
mkdirSync5(this.baseDir, { recursive: true });
|
|
1793
1783
|
return;
|
|
1794
1784
|
}
|
|
1795
1785
|
try {
|
|
1796
|
-
const raw =
|
|
1786
|
+
const raw = readFileSync8(this.plaintextFilePath, "utf-8");
|
|
1797
1787
|
const parsed = JSON.parse(raw);
|
|
1798
1788
|
this.data = {
|
|
1799
1789
|
version: 1,
|
|
@@ -1803,18 +1793,18 @@ var ReceiptArchive = class {
|
|
|
1803
1793
|
} catch {
|
|
1804
1794
|
console.error(`[ReceiptArchive] Could not parse ${this.plaintextFilePath} \u2014 preserving as .corrupt`);
|
|
1805
1795
|
try {
|
|
1806
|
-
|
|
1796
|
+
writeFileSync5(
|
|
1807
1797
|
`${this.plaintextFilePath}.corrupt-${Date.now()}`,
|
|
1808
|
-
|
|
1798
|
+
readFileSync8(this.plaintextFilePath)
|
|
1809
1799
|
);
|
|
1810
1800
|
} catch {
|
|
1811
1801
|
}
|
|
1812
1802
|
}
|
|
1813
1803
|
}
|
|
1814
1804
|
loadEncrypted() {
|
|
1815
|
-
if (!
|
|
1805
|
+
if (!existsSync7(this.encryptedFilePath)) return;
|
|
1816
1806
|
try {
|
|
1817
|
-
const raw =
|
|
1807
|
+
const raw = readFileSync8(this.encryptedFilePath, "utf-8");
|
|
1818
1808
|
const data = JSON.parse(raw);
|
|
1819
1809
|
const decrypted = this.decrypt(data.blob);
|
|
1820
1810
|
const parsed = JSON.parse(decrypted);
|
|
@@ -1826,9 +1816,9 @@ var ReceiptArchive = class {
|
|
|
1826
1816
|
} catch (err) {
|
|
1827
1817
|
console.error(`[ReceiptArchive] Could not decrypt ${this.encryptedFilePath} \u2014 preserving as .corrupt:`, err);
|
|
1828
1818
|
try {
|
|
1829
|
-
|
|
1819
|
+
writeFileSync5(
|
|
1830
1820
|
`${this.encryptedFilePath}.corrupt-${Date.now()}`,
|
|
1831
|
-
|
|
1821
|
+
readFileSync8(this.encryptedFilePath)
|
|
1832
1822
|
);
|
|
1833
1823
|
} catch {
|
|
1834
1824
|
}
|
|
@@ -1842,29 +1832,29 @@ var ReceiptArchive = class {
|
|
|
1842
1832
|
}
|
|
1843
1833
|
}
|
|
1844
1834
|
persistPlaintext() {
|
|
1845
|
-
|
|
1846
|
-
|
|
1835
|
+
mkdirSync5(this.baseDir, { recursive: true, mode: 448 });
|
|
1836
|
+
writeFileSync5(this.plaintextFilePath, JSON.stringify(this.data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1847
1837
|
}
|
|
1848
1838
|
persistEncrypted() {
|
|
1849
1839
|
const data = {
|
|
1850
1840
|
version: 1,
|
|
1851
1841
|
blob: this.encrypt(JSON.stringify(this.data))
|
|
1852
1842
|
};
|
|
1853
|
-
|
|
1854
|
-
|
|
1843
|
+
mkdirSync5(this.baseDir, { recursive: true, mode: 448 });
|
|
1844
|
+
writeFileSync5(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1855
1845
|
}
|
|
1856
1846
|
};
|
|
1857
1847
|
|
|
1858
1848
|
// src/lib/execution-journal.ts
|
|
1859
|
-
import { mkdirSync as
|
|
1849
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync8, renameSync } from "fs";
|
|
1860
1850
|
import { homedir as homedir5 } from "os";
|
|
1861
|
-
import { dirname as
|
|
1862
|
-
import { createHash as
|
|
1863
|
-
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ??
|
|
1851
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
1852
|
+
import { createHash as createHash2 } from "crypto";
|
|
1853
|
+
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ?? join8(homedir5(), ".suveren");
|
|
1864
1854
|
var FILE_NAME = "execution-journal.json";
|
|
1865
1855
|
var MAX_AGE_SECONDS2 = 90 * 24 * 60 * 60;
|
|
1866
1856
|
function hashToolArgs(args) {
|
|
1867
|
-
return "sha256:" +
|
|
1857
|
+
return "sha256:" + createHash2("sha256").update(stableStringify(args)).digest("hex");
|
|
1868
1858
|
}
|
|
1869
1859
|
function stableStringify(value) {
|
|
1870
1860
|
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
|
|
@@ -1876,8 +1866,8 @@ function stableStringify(value) {
|
|
|
1876
1866
|
var ExecutionJournal = class {
|
|
1877
1867
|
filePath;
|
|
1878
1868
|
constructor(filePath) {
|
|
1879
|
-
const baseDir = filePath && filePath.endsWith(".json") ?
|
|
1880
|
-
this.filePath =
|
|
1869
|
+
const baseDir = filePath && filePath.endsWith(".json") ? dirname6(filePath) : filePath ?? DEFAULT_DIR5;
|
|
1870
|
+
this.filePath = join8(baseDir, FILE_NAME);
|
|
1881
1871
|
}
|
|
1882
1872
|
/** Look a ticket up. Always reads from disk — another process may have written. */
|
|
1883
1873
|
get(ticketId) {
|
|
@@ -1916,9 +1906,9 @@ var ExecutionJournal = class {
|
|
|
1916
1906
|
}
|
|
1917
1907
|
// ─── Persistence ────────────────────────────────────────────────────────
|
|
1918
1908
|
load() {
|
|
1919
|
-
if (!
|
|
1909
|
+
if (!existsSync8(this.filePath)) return { version: 1, entries: [] };
|
|
1920
1910
|
try {
|
|
1921
|
-
const parsed = JSON.parse(
|
|
1911
|
+
const parsed = JSON.parse(readFileSync9(this.filePath, "utf8"));
|
|
1922
1912
|
if (parsed?.version !== 1 || !Array.isArray(parsed.entries)) {
|
|
1923
1913
|
throw new Error("unexpected shape");
|
|
1924
1914
|
}
|
|
@@ -1932,9 +1922,9 @@ var ExecutionJournal = class {
|
|
|
1932
1922
|
persist(file) {
|
|
1933
1923
|
const cutoff = nowSec() - MAX_AGE_SECONDS2;
|
|
1934
1924
|
file.entries = file.entries.filter((e) => e.startedAt >= cutoff);
|
|
1935
|
-
|
|
1925
|
+
mkdirSync6(dirname6(this.filePath), { recursive: true, mode: 448 });
|
|
1936
1926
|
const tmp = this.filePath + ".tmp";
|
|
1937
|
-
|
|
1927
|
+
writeFileSync6(tmp, JSON.stringify(file, null, 2), { mode: 384 });
|
|
1938
1928
|
renameSync(tmp, this.filePath);
|
|
1939
1929
|
}
|
|
1940
1930
|
};
|
|
@@ -1943,10 +1933,10 @@ function nowSec() {
|
|
|
1943
1933
|
}
|
|
1944
1934
|
|
|
1945
1935
|
// src/lib/proposal-submission-store.ts
|
|
1946
|
-
import { mkdirSync as
|
|
1936
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync9, renameSync as renameSync2 } from "fs";
|
|
1947
1937
|
import { homedir as homedir6 } from "os";
|
|
1948
|
-
import { dirname as
|
|
1949
|
-
var DEFAULT_DIR6 = process.env.SUVEREN_DATA_DIR ??
|
|
1938
|
+
import { dirname as dirname7, join as join9 } from "path";
|
|
1939
|
+
var DEFAULT_DIR6 = process.env.SUVEREN_DATA_DIR ?? join9(homedir6(), ".suveren");
|
|
1950
1940
|
var FILE_NAME2 = "proposal-submissions.json";
|
|
1951
1941
|
var MAX_AGE_SECONDS3 = 90 * 24 * 60 * 60;
|
|
1952
1942
|
function nowSec2() {
|
|
@@ -1955,8 +1945,8 @@ function nowSec2() {
|
|
|
1955
1945
|
var ProposalSubmissionStore = class {
|
|
1956
1946
|
filePath;
|
|
1957
1947
|
constructor(filePath) {
|
|
1958
|
-
const baseDir = filePath && filePath.endsWith(".json") ?
|
|
1959
|
-
this.filePath =
|
|
1948
|
+
const baseDir = filePath && filePath.endsWith(".json") ? dirname7(filePath) : filePath ?? DEFAULT_DIR6;
|
|
1949
|
+
this.filePath = join9(baseDir, FILE_NAME2);
|
|
1960
1950
|
}
|
|
1961
1951
|
/** Record what was submitted, right after a successful submitProposal(). */
|
|
1962
1952
|
record(entry) {
|
|
@@ -1979,9 +1969,9 @@ var ProposalSubmissionStore = class {
|
|
|
1979
1969
|
return this.load().entries.find((e) => e.proposalId === proposalId);
|
|
1980
1970
|
}
|
|
1981
1971
|
load() {
|
|
1982
|
-
if (!
|
|
1972
|
+
if (!existsSync9(this.filePath)) return { version: 1, entries: [] };
|
|
1983
1973
|
try {
|
|
1984
|
-
const parsed = JSON.parse(
|
|
1974
|
+
const parsed = JSON.parse(readFileSync10(this.filePath, "utf-8"));
|
|
1985
1975
|
const cutoff = nowSec2() - MAX_AGE_SECONDS3;
|
|
1986
1976
|
parsed.entries = (parsed.entries ?? []).filter((e) => e.submittedAt >= cutoff);
|
|
1987
1977
|
return parsed;
|
|
@@ -1990,9 +1980,9 @@ var ProposalSubmissionStore = class {
|
|
|
1990
1980
|
}
|
|
1991
1981
|
}
|
|
1992
1982
|
persist(file) {
|
|
1993
|
-
|
|
1983
|
+
mkdirSync7(dirname7(this.filePath), { recursive: true, mode: 448 });
|
|
1994
1984
|
const tmpPath = `${this.filePath}.tmp`;
|
|
1995
|
-
|
|
1985
|
+
writeFileSync7(tmpPath, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1996
1986
|
renameSync2(tmpPath, this.filePath);
|
|
1997
1987
|
}
|
|
1998
1988
|
};
|
|
@@ -2088,6 +2078,666 @@ var MCPGatekeeper = class {
|
|
|
2088
2078
|
}
|
|
2089
2079
|
};
|
|
2090
2080
|
|
|
2081
|
+
// src/lib/report/report-store.ts
|
|
2082
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync10, unlinkSync as unlinkSync5 } from "fs";
|
|
2083
|
+
import { homedir as homedir7 } from "os";
|
|
2084
|
+
import { dirname as dirname8, join as join10 } from "path";
|
|
2085
|
+
import { createCipheriv as createCipheriv5, createDecipheriv as createDecipheriv5, randomBytes as randomBytes5 } from "crypto";
|
|
2086
|
+
|
|
2087
|
+
// src/lib/report-brief.ts
|
|
2088
|
+
var REPORT_ELEMENTS = [
|
|
2089
|
+
"sv-ticket",
|
|
2090
|
+
"sv-approval",
|
|
2091
|
+
"sv-mandate",
|
|
2092
|
+
"sv-record",
|
|
2093
|
+
"sv-case",
|
|
2094
|
+
"sv-metric"
|
|
2095
|
+
];
|
|
2096
|
+
var REPORT_BRIEF = `How to write the report
|
|
2097
|
+
|
|
2098
|
+
Purpose: show the company's management what the AI achieved in this test \u2014 case by case, from the request that started it to the goal that completed it \u2014 with proof for every fact that matters.
|
|
2099
|
+
|
|
2100
|
+
Who reads it: managers who are not technical. Plain words, short sentences, the result first.
|
|
2101
|
+
|
|
2102
|
+
What you write: one HTML page (HTML, CSS and inline SVG only \u2014 no JavaScript, no external files or links except the check links the gateway adds). The layout, headings, charts, tables and your assessment are yours.
|
|
2103
|
+
|
|
2104
|
+
It must read well everywhere: on a phone, in a narrow side panel (about 360 pixels wide) and on a large screen. Use fluid widths (no fixed page widths), let columns stack on narrow screens (CSS grid/flex with wrapping, or media queries), make wide tables scroll horizontally or turn into stacked rows, give SVG charts a viewBox so they scale, and keep text at a readable size.
|
|
2105
|
+
|
|
2106
|
+
What you cannot write yourself: facts that need proof. Place these elements instead; the gateway looks the data up, checks it and draws the element. You give references, never the content:
|
|
2107
|
+
|
|
2108
|
+
- <sv-ticket ref="TICKET_ID"></sv-ticket> \u2014 one action you took, with its signed ticket.
|
|
2109
|
+
- <sv-approval ticket="TICKET_ID"></sv-approval> \u2014 who approved that action, when, and how long it waited.
|
|
2110
|
+
- <sv-mandate ticket="TICKET_ID"></sv-mandate> \u2014 the authority the action ran under: limits, mode, intent.
|
|
2111
|
+
- <sv-record system="email|crm|erp" ref="RECORD_ID"></sv-record> \u2014 an email, CRM entry, quote or order.
|
|
2112
|
+
- <sv-case start="email:MESSAGE_ID" goal="ticket:TICKET_ID" steps="TICKET_ID TICKET_ID"></sv-case> \u2014 one business case: the incoming email that started it, the ticket that completed it, and the tickets in between.
|
|
2113
|
+
- <sv-metric kind="KIND" cases="all"></sv-metric> \u2014 a number the gateway computes over the cases you defined. KIND: completed, median-time, average-time, without-approval, approvals, median-approval-wait, tickets, refusals. cases is "all" or a space-separated list of case ids, e.g. cases="C1 C3".
|
|
2114
|
+
|
|
2115
|
+
Rules:
|
|
2116
|
+
1. Define every case you worked on with sv-case \u2014 also the ones that did not reach their goal (omit goal, or say why in your text). The gateway shows management how many of the loaded cases your report covers and names the missing ones \u2014 and lists every ticket from the test period that your report does not reference, so name every step.
|
|
2117
|
+
2. Use sv-metric for every headline number. Everything you write yourself \u2014 text, tables, charts, numbers you compute \u2014 sits under the gateway's label "AI analysis \u2014 not verified"; only the drawn elements carry the gateway's green check. Do not imitate them: class names starting with "sv-" are removed from your HTML.
|
|
2118
|
+
3. A case's goal must be what actually completed it \u2014 for an email reply, the reply to the start email. The gateway checks this link. A case is timed from when its email reached the test (the test data may date its emails earlier \u2014 the gateway uses the later of the two), so steps from before that moment do not belong to it.
|
|
2119
|
+
4. A wrong or unknown reference is shown as "not verifiable". Do not guess IDs \u2014 read them first.
|
|
2120
|
+
5. Never claim more than the elements prove. Say plainly what went wrong or waited for a person.
|
|
2121
|
+
|
|
2122
|
+
Suggested structure:
|
|
2123
|
+
1. The result in one sentence, then 3\u20134 sv-metric figures (completed, median time, share without approval, approvals).
|
|
2124
|
+
2. All cases at a glance \u2014 a table with one row per case, linked to its sv-case below.
|
|
2125
|
+
3. Each case: sv-case, then the sv-ticket and sv-approval elements that explain it.
|
|
2126
|
+
4. What was stopped or needed a person, and why (sv-approval, refusals).
|
|
2127
|
+
5. Your assessment: what worked, what slowed things down, what a different mandate would change \u2014 marked as your view.
|
|
2128
|
+
|
|
2129
|
+
Update the report whenever cases progress; each write replaces the previous report.`;
|
|
2130
|
+
|
|
2131
|
+
// src/lib/report/sanitize.ts
|
|
2132
|
+
import sanitizeHtmlLib from "sanitize-html";
|
|
2133
|
+
var FORBIDDEN_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "link"]);
|
|
2134
|
+
function isUrlAttribute(name) {
|
|
2135
|
+
const n = name.toLowerCase();
|
|
2136
|
+
return n === "href" || n === "src" || n === "xlink:href" || n.endsWith(":href");
|
|
2137
|
+
}
|
|
2138
|
+
function isAllowedUrl(value) {
|
|
2139
|
+
const v = value.trim();
|
|
2140
|
+
if (v.startsWith("#")) return true;
|
|
2141
|
+
if (/^data:image\//i.test(v)) return true;
|
|
2142
|
+
return false;
|
|
2143
|
+
}
|
|
2144
|
+
function stripGatewayClasses(value) {
|
|
2145
|
+
return value.split(/\s+/).filter((c) => c && !/^sv-/i.test(c)).join(" ");
|
|
2146
|
+
}
|
|
2147
|
+
function sanitizeReportHtml(html) {
|
|
2148
|
+
return sanitizeHtmlLib(html, {
|
|
2149
|
+
// Allow every tag/attribute by default — see module doc comment for why
|
|
2150
|
+
// this is deny-by-name rather than allow-by-name. `transformTags['*']`
|
|
2151
|
+
// below is then the ONLY attribute filter (allowedAttributes: false
|
|
2152
|
+
// skips sanitize-html's own, so there is exactly one place attributes
|
|
2153
|
+
// are decided, not two that could disagree). Logs a harmless warning for
|
|
2154
|
+
// "script"/"style" on every call (sanitize-html's own vulnerable-tag
|
|
2155
|
+
// notice, fired from its internal tag list even though we pass `false`,
|
|
2156
|
+
// not an array containing them) — `allowVulnerableTags` is the
|
|
2157
|
+
// documented way to accept that, and <script> is still removed, by
|
|
2158
|
+
// `exclusiveFilter` below, regardless of this flag.
|
|
2159
|
+
allowedTags: false,
|
|
2160
|
+
allowedAttributes: false,
|
|
2161
|
+
allowVulnerableTags: true,
|
|
2162
|
+
// sanitize-html's OWN scheme check (independent of transformTags, and
|
|
2163
|
+
// applied to it/src/cite only — NOT xlink:href, which is why the
|
|
2164
|
+
// transformTags filter above is the one actually relied on for SVG) uses
|
|
2165
|
+
// this list; it has no "data" scheme by default, which would undo our
|
|
2166
|
+
// own decision to keep `data:image/*` sources. Nothing wider is opened:
|
|
2167
|
+
// transformTags has already removed every href/src that isn't a
|
|
2168
|
+
// fragment link or a data:image/* URI by the time this check runs.
|
|
2169
|
+
allowedSchemes: [...sanitizeHtmlLib.defaults.allowedSchemes, "data"],
|
|
2170
|
+
// Preserve attribute name casing — inline SVG uses camelCase attributes
|
|
2171
|
+
// (viewBox, preserveAspectRatio, …) that lowercasing would silently
|
|
2172
|
+
// break. Safe for the `on*`/url checks below: both compare case-folded.
|
|
2173
|
+
parser: { lowerCaseAttributeNames: false },
|
|
2174
|
+
// Removes the named tag AND all of its children/text — the only correct
|
|
2175
|
+
// behaviour for <script>: dropping just the tag would leave its JS as
|
|
2176
|
+
// visible text, but the content must not survive either.
|
|
2177
|
+
exclusiveFilter: (frame) => {
|
|
2178
|
+
const tag = frame.tag.toLowerCase();
|
|
2179
|
+
if (FORBIDDEN_TAGS.has(tag)) return true;
|
|
2180
|
+
if (tag === "meta" && Object.keys(frame.attribs).some((a) => a.toLowerCase() === "http-equiv")) {
|
|
2181
|
+
return true;
|
|
2182
|
+
}
|
|
2183
|
+
return false;
|
|
2184
|
+
},
|
|
2185
|
+
transformTags: {
|
|
2186
|
+
"*": (tagName, attribs) => {
|
|
2187
|
+
const kept = {};
|
|
2188
|
+
for (const [name, value] of Object.entries(attribs)) {
|
|
2189
|
+
const lower = name.toLowerCase();
|
|
2190
|
+
if (lower.startsWith("on")) continue;
|
|
2191
|
+
if (isUrlAttribute(name) && !isAllowedUrl(value)) continue;
|
|
2192
|
+
if (lower.startsWith("data-sv")) continue;
|
|
2193
|
+
if (lower === "class") {
|
|
2194
|
+
const classes = stripGatewayClasses(value);
|
|
2195
|
+
if (classes) kept[name] = classes;
|
|
2196
|
+
continue;
|
|
2197
|
+
}
|
|
2198
|
+
kept[name] = value;
|
|
2199
|
+
}
|
|
2200
|
+
return { tagName, attribs: kept };
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// src/lib/report/record-resolvers.ts
|
|
2207
|
+
function findById(rows, id) {
|
|
2208
|
+
return rows.find((r) => r.id === id);
|
|
2209
|
+
}
|
|
2210
|
+
function resolveEmailRecord(exportData, ref) {
|
|
2211
|
+
const inInbox = findById(exportData.inbox, ref);
|
|
2212
|
+
if (inInbox) return { status: "verified", data: { kind: "message", folder: "inbox", ...inInbox } };
|
|
2213
|
+
const inSent = findById(exportData.sent, ref);
|
|
2214
|
+
if (inSent) return { status: "verified", data: { kind: "message", folder: "sent", ...inSent } };
|
|
2215
|
+
const change = findById(exportData.changes, ref);
|
|
2216
|
+
if (change) return { status: "verified", data: { kind: "change", causingReceiptId: change.receipt_id ?? null, ...change } };
|
|
2217
|
+
const refusal = findById(exportData.refusals, ref);
|
|
2218
|
+
if (refusal) return { status: "verified", data: { kind: "refusal", ...refusal } };
|
|
2219
|
+
return { status: "unverifiable", reason: `No email record "${ref}" in the simulator export.` };
|
|
2220
|
+
}
|
|
2221
|
+
function resolveErpRecord(exportData, ref) {
|
|
2222
|
+
const quote = findById(exportData.quotes, ref) ?? exportData.quotes.find((q) => q.number === ref);
|
|
2223
|
+
if (quote) return { status: "verified", data: { kind: "quote", ...quote } };
|
|
2224
|
+
const order = findById(exportData.orders, ref) ?? exportData.orders.find((o) => o.number === ref);
|
|
2225
|
+
if (order) return { status: "verified", data: { kind: "order", ...order } };
|
|
2226
|
+
const change = findById(exportData.changes, ref);
|
|
2227
|
+
if (change) return { status: "verified", data: { kind: "change", causingReceiptId: change.receipt_id ?? null, ...change } };
|
|
2228
|
+
const refusal = findById(exportData.refusals, ref);
|
|
2229
|
+
if (refusal) return { status: "verified", data: { kind: "refusal", ...refusal } };
|
|
2230
|
+
return { status: "unverifiable", reason: `No ERP record "${ref}" in the simulator export.` };
|
|
2231
|
+
}
|
|
2232
|
+
function resolveCrmRecord(exportData, ref) {
|
|
2233
|
+
for (const [kind, rows] of [
|
|
2234
|
+
["contact", exportData.contacts],
|
|
2235
|
+
["deal", exportData.deals],
|
|
2236
|
+
["task", exportData.tasks],
|
|
2237
|
+
["activity", exportData.activities]
|
|
2238
|
+
]) {
|
|
2239
|
+
const row = findById(rows, ref);
|
|
2240
|
+
if (row) return { status: "verified", data: { kind, ...row } };
|
|
2241
|
+
}
|
|
2242
|
+
const change = findById(exportData.changes, ref);
|
|
2243
|
+
if (change) return { status: "verified", data: { kind: "change", causingReceiptId: change.receipt_id ?? null, ...change } };
|
|
2244
|
+
const refusal = findById(exportData.refusals, ref);
|
|
2245
|
+
if (refusal) return { status: "verified", data: { kind: "refusal", ...refusal } };
|
|
2246
|
+
return { status: "unverifiable", reason: `No CRM record "${ref}" in the simulator export.` };
|
|
2247
|
+
}
|
|
2248
|
+
function resolveRecord(system, exportData, ref) {
|
|
2249
|
+
if (system === "email") return resolveEmailRecord(exportData, ref);
|
|
2250
|
+
if (system === "erp") return resolveErpRecord(exportData, ref);
|
|
2251
|
+
return resolveCrmRecord(exportData, ref);
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
// src/lib/report/metric-resolvers.ts
|
|
2255
|
+
var METRIC_KINDS = [
|
|
2256
|
+
"completed",
|
|
2257
|
+
"median-time",
|
|
2258
|
+
"average-time",
|
|
2259
|
+
"without-approval",
|
|
2260
|
+
"approvals",
|
|
2261
|
+
"median-approval-wait",
|
|
2262
|
+
"tickets",
|
|
2263
|
+
"refusals"
|
|
2264
|
+
];
|
|
2265
|
+
var UnknownMetricKindError = class extends Error {
|
|
2266
|
+
constructor(kind) {
|
|
2267
|
+
super(`Unknown sv-metric kind "${kind}" \u2014 expected one of: ${METRIC_KINDS.join(", ")}.`);
|
|
2268
|
+
this.name = "UnknownMetricKindError";
|
|
2269
|
+
}
|
|
2270
|
+
};
|
|
2271
|
+
var START_TIME_METRICS = /* @__PURE__ */ new Set(["median-time", "average-time", "refusals"]);
|
|
2272
|
+
function median(values) {
|
|
2273
|
+
if (values.length === 0) return 0;
|
|
2274
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
2275
|
+
const mid = Math.floor(sorted.length / 2);
|
|
2276
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
2277
|
+
}
|
|
2278
|
+
function average(values) {
|
|
2279
|
+
if (values.length === 0) return 0;
|
|
2280
|
+
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
2281
|
+
}
|
|
2282
|
+
function computeMetric(kind, cases, refusalTimes = []) {
|
|
2283
|
+
if (START_TIME_METRICS.has(kind) && cases.some((c) => c.totalDurationSeconds === null)) {
|
|
2284
|
+
throw new Error("Case start times are not verifiable.");
|
|
2285
|
+
}
|
|
2286
|
+
switch (kind) {
|
|
2287
|
+
case "completed":
|
|
2288
|
+
return cases.length;
|
|
2289
|
+
case "median-time":
|
|
2290
|
+
return median(cases.map((c) => c.totalDurationSeconds));
|
|
2291
|
+
case "average-time":
|
|
2292
|
+
return average(cases.map((c) => c.totalDurationSeconds));
|
|
2293
|
+
case "without-approval": {
|
|
2294
|
+
if (cases.length === 0) return 0;
|
|
2295
|
+
const withoutApproval = cases.filter((c) => c.approvals.length === 0).length;
|
|
2296
|
+
return withoutApproval / cases.length;
|
|
2297
|
+
}
|
|
2298
|
+
case "approvals":
|
|
2299
|
+
return cases.reduce((sum, c) => sum + c.approvals.length, 0);
|
|
2300
|
+
case "median-approval-wait":
|
|
2301
|
+
return median(cases.flatMap((c) => c.approvals.map((a) => a.waitSeconds).filter((w) => typeof w === "number")));
|
|
2302
|
+
case "tickets": {
|
|
2303
|
+
const ids = new Set(cases.flatMap((c) => c.ticketIds));
|
|
2304
|
+
return ids.size;
|
|
2305
|
+
}
|
|
2306
|
+
case "refusals":
|
|
2307
|
+
return cases.reduce(
|
|
2308
|
+
(sum, c) => sum + refusalTimes.filter((t) => t >= c.startTime && t <= c.goalTime).length,
|
|
2309
|
+
0
|
|
2310
|
+
);
|
|
2311
|
+
default:
|
|
2312
|
+
throw new UnknownMetricKindError(kind);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
// src/lib/report/types.ts
|
|
2317
|
+
function isEmailExport(v) {
|
|
2318
|
+
const o = v;
|
|
2319
|
+
return !!o && Array.isArray(o.inbox) && Array.isArray(o.sent) && Array.isArray(o.changes) && Array.isArray(o.refusals);
|
|
2320
|
+
}
|
|
2321
|
+
function isErpExport(v) {
|
|
2322
|
+
const o = v;
|
|
2323
|
+
return !!o && Array.isArray(o.quotes) && Array.isArray(o.orders) && Array.isArray(o.changes) && Array.isArray(o.refusals);
|
|
2324
|
+
}
|
|
2325
|
+
function isCrmExport(v) {
|
|
2326
|
+
const o = v;
|
|
2327
|
+
return !!o && Array.isArray(o.contacts) && Array.isArray(o.deals) && Array.isArray(o.changes) && Array.isArray(o.refusals);
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
// src/lib/report/verify-report.ts
|
|
2331
|
+
var KNOWN_KINDS = new Set(REPORT_ELEMENTS);
|
|
2332
|
+
async function verifyReport(html, sources) {
|
|
2333
|
+
const sanitized = sanitizeReportHtml(html);
|
|
2334
|
+
const parsed = parseElements(sanitized);
|
|
2335
|
+
const exportCache = /* @__PURE__ */ new Map();
|
|
2336
|
+
function getExport(system) {
|
|
2337
|
+
let p = exportCache.get(system);
|
|
2338
|
+
if (!p) {
|
|
2339
|
+
p = sources.runExport(system).then(
|
|
2340
|
+
(data) => ({ ok: true, data }),
|
|
2341
|
+
(err) => ({ ok: false, reason: err instanceof Error ? err.message : String(err) })
|
|
2342
|
+
);
|
|
2343
|
+
exportCache.set(system, p);
|
|
2344
|
+
}
|
|
2345
|
+
return p;
|
|
2346
|
+
}
|
|
2347
|
+
const elements = [];
|
|
2348
|
+
const caseElements = [];
|
|
2349
|
+
const metricParsed = [];
|
|
2350
|
+
async function resolveOne(p) {
|
|
2351
|
+
try {
|
|
2352
|
+
switch (p.kind) {
|
|
2353
|
+
case "sv-ticket": {
|
|
2354
|
+
const r = await resolveTicketElement(sources.archive, p.attrs.ref ?? "");
|
|
2355
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, ...r };
|
|
2356
|
+
}
|
|
2357
|
+
case "sv-approval": {
|
|
2358
|
+
const r = await resolveApprovalElement(sources.archive, p.attrs.ticket ?? "");
|
|
2359
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, ...r };
|
|
2360
|
+
}
|
|
2361
|
+
case "sv-mandate": {
|
|
2362
|
+
const r = await resolveMandateElement(sources.archive, p.attrs.ticket ?? "");
|
|
2363
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, ...r };
|
|
2364
|
+
}
|
|
2365
|
+
case "sv-record": {
|
|
2366
|
+
const system = p.attrs.system;
|
|
2367
|
+
if (system !== "email" && system !== "crm" && system !== "erp") {
|
|
2368
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, status: "unverifiable", reason: `Unknown system "${p.attrs.system ?? ""}".` };
|
|
2369
|
+
}
|
|
2370
|
+
const exp = await getExport(system);
|
|
2371
|
+
if (!exp.ok) {
|
|
2372
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, status: "unverifiable", reason: `Could not read the ${system} simulator export: ${exp.reason}` };
|
|
2373
|
+
}
|
|
2374
|
+
const r = resolveRecord(system, exp.data, p.attrs.ref ?? "");
|
|
2375
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, ...r };
|
|
2376
|
+
}
|
|
2377
|
+
case "sv-case": {
|
|
2378
|
+
const exp = await getExport("email");
|
|
2379
|
+
if (!exp.ok || !isEmailExport(exp.data)) {
|
|
2380
|
+
return {
|
|
2381
|
+
id: p.id,
|
|
2382
|
+
kind: p.kind,
|
|
2383
|
+
attrs: p.attrs,
|
|
2384
|
+
status: "unverifiable",
|
|
2385
|
+
reason: `Could not read the email simulator export: ${!exp.ok ? exp.reason : "unexpected shape"}`
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
const resolution = await resolveCaseElement(sources.archive, exp.data, p.attrs);
|
|
2389
|
+
const el = {
|
|
2390
|
+
id: p.id,
|
|
2391
|
+
kind: p.kind,
|
|
2392
|
+
attrs: p.attrs,
|
|
2393
|
+
status: resolution.status,
|
|
2394
|
+
...resolution.reason ? { reason: resolution.reason } : {},
|
|
2395
|
+
...resolution.data ? { data: resolution.data } : {}
|
|
2396
|
+
};
|
|
2397
|
+
caseElements.push({ el, resolution });
|
|
2398
|
+
return el;
|
|
2399
|
+
}
|
|
2400
|
+
default:
|
|
2401
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, status: "unverifiable", reason: "Unhandled element kind." };
|
|
2402
|
+
}
|
|
2403
|
+
} catch (err) {
|
|
2404
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, status: "unverifiable", reason: err instanceof Error ? err.message : String(err) };
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
for (const p of parsed) {
|
|
2408
|
+
if (!KNOWN_KINDS.has(p.kind)) {
|
|
2409
|
+
elements.push({
|
|
2410
|
+
id: p.id,
|
|
2411
|
+
kind: p.kind,
|
|
2412
|
+
attrs: p.attrs,
|
|
2413
|
+
status: "unverifiable",
|
|
2414
|
+
reason: `Unknown element "${p.kind}" \u2014 not one of the six elements report-brief.ts defines.`
|
|
2415
|
+
});
|
|
2416
|
+
continue;
|
|
2417
|
+
}
|
|
2418
|
+
if (p.kind === "sv-metric") {
|
|
2419
|
+
metricParsed.push(p);
|
|
2420
|
+
continue;
|
|
2421
|
+
}
|
|
2422
|
+
elements.push(await resolveOne(p));
|
|
2423
|
+
}
|
|
2424
|
+
for (const p of metricParsed) {
|
|
2425
|
+
try {
|
|
2426
|
+
const kind = p.attrs.kind ?? "";
|
|
2427
|
+
const casesAttr = (p.attrs.cases ?? "all").trim();
|
|
2428
|
+
const requestedCount = casesAttr === "all" ? caseElements.length : new Set(casesAttr.split(/\s+/).filter(Boolean)).size;
|
|
2429
|
+
let selected = caseElements.filter((c) => c.resolution.status !== "unverifiable" && c.resolution.data);
|
|
2430
|
+
if (casesAttr !== "all") {
|
|
2431
|
+
const wanted = new Set(casesAttr.split(/\s+/).filter(Boolean));
|
|
2432
|
+
selected = selected.filter((c) => wanted.has(c.resolution.data.caseId));
|
|
2433
|
+
}
|
|
2434
|
+
const caseInputs = selected.map((c) => ({
|
|
2435
|
+
caseId: c.resolution.data.caseId,
|
|
2436
|
+
startTime: c.resolution.data.start.time,
|
|
2437
|
+
goalTime: c.resolution.data.goal.time,
|
|
2438
|
+
totalDurationSeconds: c.resolution.data.totalDurationSeconds,
|
|
2439
|
+
ticketIds: [c.resolution.data.goal.ticketId, ...c.resolution.data.steps.map((s) => s.ticketId)],
|
|
2440
|
+
approvals: c.resolution.data.approvals
|
|
2441
|
+
}));
|
|
2442
|
+
const verifiedCount = caseInputs.length;
|
|
2443
|
+
if (verifiedCount === 0) {
|
|
2444
|
+
elements.push({
|
|
2445
|
+
id: p.id,
|
|
2446
|
+
kind: p.kind,
|
|
2447
|
+
attrs: p.attrs,
|
|
2448
|
+
status: "unverifiable",
|
|
2449
|
+
reason: requestedCount > 0 ? `No verified cases (0 of ${requestedCount} requested) \u2014 no figure.` : "No verified cases \u2014 no figure."
|
|
2450
|
+
});
|
|
2451
|
+
continue;
|
|
2452
|
+
}
|
|
2453
|
+
if (START_TIME_METRICS.has(kind) && caseInputs.some((c) => c.totalDurationSeconds === null)) {
|
|
2454
|
+
elements.push({ id: p.id, kind: p.kind, attrs: p.attrs, status: "unverifiable", reason: CASE_TIME_UNKNOWN_REASON });
|
|
2455
|
+
continue;
|
|
2456
|
+
}
|
|
2457
|
+
const refusalTimes = kind === "refusals" ? await collectRefusalTimes(getExport) : [];
|
|
2458
|
+
const value = computeMetric(kind, caseInputs, refusalTimes);
|
|
2459
|
+
const data = { kind, cases: casesAttr, value, caseCount: verifiedCount };
|
|
2460
|
+
if (verifiedCount < requestedCount) {
|
|
2461
|
+
elements.push({
|
|
2462
|
+
id: p.id,
|
|
2463
|
+
kind: p.kind,
|
|
2464
|
+
attrs: p.attrs,
|
|
2465
|
+
status: "warning",
|
|
2466
|
+
reason: `Computed over ${verifiedCount} of ${requestedCount} requested cases \u2014 the rest did not verify.`,
|
|
2467
|
+
data
|
|
2468
|
+
});
|
|
2469
|
+
} else {
|
|
2470
|
+
elements.push({ id: p.id, kind: p.kind, attrs: p.attrs, status: "verified", data });
|
|
2471
|
+
}
|
|
2472
|
+
} catch (err) {
|
|
2473
|
+
elements.push({ id: p.id, kind: p.kind, attrs: p.attrs, status: "unverifiable", reason: err instanceof Error ? err.message : String(err) });
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
const proof = buildProof(elements, caseElements);
|
|
2477
|
+
const coverage = await buildCoverage(caseElements, getExport, sources.archive, proof.ticketsReferenced);
|
|
2478
|
+
return { html: sanitized, elements, proof, coverage };
|
|
2479
|
+
}
|
|
2480
|
+
async function collectRefusalTimes(getExport) {
|
|
2481
|
+
const times = [];
|
|
2482
|
+
for (const system of ["email", "crm", "erp"]) {
|
|
2483
|
+
const exp = await getExport(system);
|
|
2484
|
+
if (!exp.ok) throw new Error(`Could not read the ${system} simulator export: ${exp.reason}`);
|
|
2485
|
+
const refusals = exp.data.refusals ?? [];
|
|
2486
|
+
for (const r of refusals) {
|
|
2487
|
+
const t = parseTimestampSeconds(r.at);
|
|
2488
|
+
if (t !== void 0) times.push(t);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
return times;
|
|
2492
|
+
}
|
|
2493
|
+
function summarize(el) {
|
|
2494
|
+
const d = el.data ?? {};
|
|
2495
|
+
switch (el.kind) {
|
|
2496
|
+
case "sv-ticket": {
|
|
2497
|
+
const actionLabel = typeof d.actionLabel === "string" ? d.actionLabel : formatActionLabel(d.action);
|
|
2498
|
+
const timeLabel = typeof d.timeLabel === "string" ? d.timeLabel : formatDateTime(d.time);
|
|
2499
|
+
return `${actionLabel} \u2014 ${timeLabel}`;
|
|
2500
|
+
}
|
|
2501
|
+
case "sv-approval": {
|
|
2502
|
+
const who = typeof d.whoLabel === "string" ? d.whoLabel : d.who?.join(", ") ?? "unknown";
|
|
2503
|
+
const waitLabel = typeof d.waitLabel === "string" ? d.waitLabel : typeof d.waitSeconds === "number" ? formatDuration(d.waitSeconds) : "unknown";
|
|
2504
|
+
return `Approval by ${who} \u2014 waited ${waitLabel}`;
|
|
2505
|
+
}
|
|
2506
|
+
case "sv-mandate": {
|
|
2507
|
+
const profileLabel = typeof d.profileLabel === "string" ? d.profileLabel : profileShortLabel(typeof d.profile === "string" ? d.profile : void 0);
|
|
2508
|
+
const owners = Array.isArray(d.owners) && d.owners.length > 0 ? d.owners.join(", ") : "unknown owner";
|
|
2509
|
+
return `Mandate: ${profileLabel} \xB7 ${owners}`;
|
|
2510
|
+
}
|
|
2511
|
+
case "sv-record":
|
|
2512
|
+
return `${String(d.kind ?? "record")} record checked`;
|
|
2513
|
+
case "sv-case": {
|
|
2514
|
+
const steps = d.steps?.length ?? 0;
|
|
2515
|
+
const time = typeof d.totalDurationSeconds === "number" ? formatDuration(d.totalDurationSeconds) : "time not verifiable";
|
|
2516
|
+
return `Case ${String(d.caseId)}: ${steps} ${steps === 1 ? "step" : "steps"}, ${time}`;
|
|
2517
|
+
}
|
|
2518
|
+
case "sv-metric": {
|
|
2519
|
+
const kind = String(d.kind ?? "");
|
|
2520
|
+
const label = METRIC_LABELS[kind] ?? kind;
|
|
2521
|
+
return `${label} = ${formatMetricValue(kind, d.value)}`;
|
|
2522
|
+
}
|
|
2523
|
+
default:
|
|
2524
|
+
return el.id;
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
function buildProof(elements, caseElements) {
|
|
2528
|
+
const ticketsReferenced = /* @__PURE__ */ new Set();
|
|
2529
|
+
const signaturesValid = /* @__PURE__ */ new Set();
|
|
2530
|
+
let recordsChecked = 0;
|
|
2531
|
+
let unverifiableCount = 0;
|
|
2532
|
+
const verifiedValues = [];
|
|
2533
|
+
for (const el of elements) {
|
|
2534
|
+
if (el.status === "unverifiable") unverifiableCount++;
|
|
2535
|
+
if (el.status === "verified" || el.status === "warning") {
|
|
2536
|
+
verifiedValues.push({ elementId: el.id, kind: el.kind, summary: summarize(el) });
|
|
2537
|
+
}
|
|
2538
|
+
if (el.kind === "sv-ticket" && el.attrs.ref) {
|
|
2539
|
+
ticketsReferenced.add(el.attrs.ref);
|
|
2540
|
+
if (el.status !== "unverifiable") signaturesValid.add(el.attrs.ref);
|
|
2541
|
+
}
|
|
2542
|
+
if ((el.kind === "sv-approval" || el.kind === "sv-mandate") && el.attrs.ticket) {
|
|
2543
|
+
ticketsReferenced.add(el.attrs.ticket);
|
|
2544
|
+
if (el.status !== "unverifiable") signaturesValid.add(el.attrs.ticket);
|
|
2545
|
+
}
|
|
2546
|
+
if (el.kind === "sv-record" && el.status === "verified") {
|
|
2547
|
+
recordsChecked++;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
for (const { el, resolution } of caseElements) {
|
|
2551
|
+
const { goalId, stepIds } = parseCaseAttrs(el.attrs);
|
|
2552
|
+
for (const id of [goalId, ...stepIds]) {
|
|
2553
|
+
if (!id) continue;
|
|
2554
|
+
ticketsReferenced.add(id);
|
|
2555
|
+
if (resolution.status !== "unverifiable") signaturesValid.add(id);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
return {
|
|
2559
|
+
ticketsReferenced: [...ticketsReferenced],
|
|
2560
|
+
signaturesValid: signaturesValid.size,
|
|
2561
|
+
recordsChecked,
|
|
2562
|
+
unverifiableCount,
|
|
2563
|
+
verifiedValues
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
async function buildCoverage(caseElements, getExport, archive, referenced) {
|
|
2567
|
+
const exp = await getExport("email");
|
|
2568
|
+
const emailExportError = !exp.ok ? exp.reason : !isEmailExport(exp.data) ? "Email simulator export had an unexpected shape." : void 0;
|
|
2569
|
+
const loadedCases = exp.ok && isEmailExport(exp.data) ? [...new Set(exp.data.inbox.map((m) => m.case_id).filter((c) => !!c))] : [];
|
|
2570
|
+
const covered = new Set(
|
|
2571
|
+
caseElements.map((c) => c.resolution.startCaseId).filter((c) => !!c)
|
|
2572
|
+
);
|
|
2573
|
+
const coveredCases = loadedCases.filter((c) => covered.has(c));
|
|
2574
|
+
const missingCases = loadedCases.filter((c) => !covered.has(c));
|
|
2575
|
+
const loadedAt = exp.ok && isEmailExport(exp.data) ? exp.data.simulation_load?.loaded_at : void 0;
|
|
2576
|
+
const periodStart = loadedAt !== void 0 ? parseTimestampSeconds(loadedAt) ?? null : null;
|
|
2577
|
+
const inPeriod = archive.getReceipts().map((r) => ({ id: String(r.receipt.id ?? ""), time: Number(r.receipt.timestamp ?? r.archivedAt) })).filter((t) => t.id && (periodStart === null || t.time >= periodStart)).sort((a, b) => a.time - b.time).map((t) => t.id);
|
|
2578
|
+
const referencedSet = new Set(referenced);
|
|
2579
|
+
const ticketsReferenced = inPeriod.filter((id) => referencedSet.has(id));
|
|
2580
|
+
const ticketsNotReferenced = inPeriod.filter((id) => !referencedSet.has(id));
|
|
2581
|
+
return {
|
|
2582
|
+
...emailExportError ? { emailExportError } : {},
|
|
2583
|
+
loadedCases,
|
|
2584
|
+
coveredCases,
|
|
2585
|
+
missingCases,
|
|
2586
|
+
periodStart,
|
|
2587
|
+
ticketsInPeriod: inPeriod,
|
|
2588
|
+
ticketsReferenced,
|
|
2589
|
+
ticketsNotReferenced
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
// src/lib/report/report-store.ts
|
|
2594
|
+
var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ?? join10(homedir7(), ".suveren");
|
|
2595
|
+
var ReportStore = class {
|
|
2596
|
+
report = null;
|
|
2597
|
+
baseDir;
|
|
2598
|
+
vaultKey = null;
|
|
2599
|
+
constructor(filePath) {
|
|
2600
|
+
if (filePath && filePath.endsWith(".json")) {
|
|
2601
|
+
this.baseDir = dirname8(filePath);
|
|
2602
|
+
} else {
|
|
2603
|
+
this.baseDir = filePath ?? DEFAULT_DIR7;
|
|
2604
|
+
}
|
|
2605
|
+
this.loadPlaintext();
|
|
2606
|
+
}
|
|
2607
|
+
/** Set the vault key for encryption. Triggers migration from plaintext if needed. */
|
|
2608
|
+
setVaultKey(key) {
|
|
2609
|
+
this.vaultKey = key;
|
|
2610
|
+
if (existsSync10(this.encryptedFilePath)) {
|
|
2611
|
+
this.loadEncrypted();
|
|
2612
|
+
} else if (this.report !== null) {
|
|
2613
|
+
this.persistEncrypted();
|
|
2614
|
+
if (existsSync10(this.plaintextFilePath)) {
|
|
2615
|
+
try {
|
|
2616
|
+
unlinkSync5(this.plaintextFilePath);
|
|
2617
|
+
} catch {
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
/**
|
|
2623
|
+
* True when encrypted evidence exists on disk that this process cannot
|
|
2624
|
+
* read yet (no vault key). Callers MUST NOT present the store as "no
|
|
2625
|
+
* report yet" in this state — that would claim a different fact than
|
|
2626
|
+
* "the report exists but is locked".
|
|
2627
|
+
*/
|
|
2628
|
+
isLocked() {
|
|
2629
|
+
return this.vaultKey === null && existsSync10(this.encryptedFilePath);
|
|
2630
|
+
}
|
|
2631
|
+
/** The stored, already-verified report — or null if none has been saved
|
|
2632
|
+
* (and the store is not locked; check `isLocked()` first). */
|
|
2633
|
+
getReport() {
|
|
2634
|
+
return this.report;
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* Replace the current report with `html`, verifying it ONCE against
|
|
2638
|
+
* `sources` before storing. Always replaces — there is only ever one
|
|
2639
|
+
* current report (plan decision).
|
|
2640
|
+
*/
|
|
2641
|
+
async saveReport(html, sources, now = Math.floor(Date.now() / 1e3)) {
|
|
2642
|
+
const result = await verifyReport(html, sources);
|
|
2643
|
+
this.report = { html: result.html, savedAt: now, checkedAt: now, result };
|
|
2644
|
+
this.persist();
|
|
2645
|
+
return this.report;
|
|
2646
|
+
}
|
|
2647
|
+
/**
|
|
2648
|
+
* Re-run verification against the CURRENTLY stored report's html — "Check
|
|
2649
|
+
* again" in the UI. Does not change `savedAt` (the AI did not rewrite
|
|
2650
|
+
* anything); only `checkedAt` and `result` move. Returns null if there is
|
|
2651
|
+
* no report to recheck.
|
|
2652
|
+
*/
|
|
2653
|
+
async recheck(sources, now = Math.floor(Date.now() / 1e3)) {
|
|
2654
|
+
if (!this.report) return null;
|
|
2655
|
+
const result = await verifyReport(this.report.html, sources);
|
|
2656
|
+
this.report = { ...this.report, checkedAt: now, result };
|
|
2657
|
+
this.persist();
|
|
2658
|
+
return this.report;
|
|
2659
|
+
}
|
|
2660
|
+
// ─── Encryption helpers ─────────────────────────────────────────────────
|
|
2661
|
+
encrypt(plaintext) {
|
|
2662
|
+
if (!this.vaultKey) throw new Error("No vault key");
|
|
2663
|
+
const iv = randomBytes5(12);
|
|
2664
|
+
const cipher = createCipheriv5("aes-256-gcm", this.vaultKey, iv);
|
|
2665
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
2666
|
+
const tag = cipher.getAuthTag();
|
|
2667
|
+
return { iv: iv.toString("hex"), ciphertext: encrypted.toString("hex"), tag: tag.toString("hex") };
|
|
2668
|
+
}
|
|
2669
|
+
decrypt(blob) {
|
|
2670
|
+
if (!this.vaultKey) throw new Error("No vault key");
|
|
2671
|
+
const decipher = createDecipheriv5("aes-256-gcm", this.vaultKey, Buffer.from(blob.iv, "hex"));
|
|
2672
|
+
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
2673
|
+
const decrypted = Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]);
|
|
2674
|
+
return decrypted.toString("utf8");
|
|
2675
|
+
}
|
|
2676
|
+
// ─── File paths ─────────────────────────────────────────────────────────
|
|
2677
|
+
get plaintextFilePath() {
|
|
2678
|
+
return join10(this.baseDir, "report.json");
|
|
2679
|
+
}
|
|
2680
|
+
get encryptedFilePath() {
|
|
2681
|
+
return join10(this.baseDir, "report.enc.json");
|
|
2682
|
+
}
|
|
2683
|
+
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
2684
|
+
loadPlaintext() {
|
|
2685
|
+
if (!existsSync10(this.plaintextFilePath)) {
|
|
2686
|
+
mkdirSync8(this.baseDir, { recursive: true });
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
try {
|
|
2690
|
+
const raw = readFileSync11(this.plaintextFilePath, "utf-8");
|
|
2691
|
+
const parsed = JSON.parse(raw);
|
|
2692
|
+
this.report = parsed.report ?? null;
|
|
2693
|
+
} catch {
|
|
2694
|
+
console.error(`[ReportStore] Could not parse ${this.plaintextFilePath} \u2014 preserving as .corrupt`);
|
|
2695
|
+
try {
|
|
2696
|
+
writeFileSync8(`${this.plaintextFilePath}.corrupt-${Date.now()}`, readFileSync11(this.plaintextFilePath));
|
|
2697
|
+
} catch {
|
|
2698
|
+
}
|
|
2699
|
+
this.report = null;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
loadEncrypted() {
|
|
2703
|
+
if (!existsSync10(this.encryptedFilePath)) return;
|
|
2704
|
+
try {
|
|
2705
|
+
const raw = readFileSync11(this.encryptedFilePath, "utf-8");
|
|
2706
|
+
const data = JSON.parse(raw);
|
|
2707
|
+
const decrypted = this.decrypt(data.blob);
|
|
2708
|
+
const parsed = JSON.parse(decrypted);
|
|
2709
|
+
this.report = parsed.report ?? null;
|
|
2710
|
+
} catch (err) {
|
|
2711
|
+
console.error(`[ReportStore] Could not decrypt ${this.encryptedFilePath} \u2014 preserving as .corrupt:`, err);
|
|
2712
|
+
try {
|
|
2713
|
+
writeFileSync8(`${this.encryptedFilePath}.corrupt-${Date.now()}`, readFileSync11(this.encryptedFilePath));
|
|
2714
|
+
} catch {
|
|
2715
|
+
}
|
|
2716
|
+
this.report = null;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
persist() {
|
|
2720
|
+
if (this.vaultKey) {
|
|
2721
|
+
this.persistEncrypted();
|
|
2722
|
+
} else {
|
|
2723
|
+
this.persistPlaintext();
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
persistPlaintext() {
|
|
2727
|
+
const data = { version: 1, report: this.report };
|
|
2728
|
+
mkdirSync8(this.baseDir, { recursive: true, mode: 448 });
|
|
2729
|
+
writeFileSync8(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2730
|
+
}
|
|
2731
|
+
persistEncrypted() {
|
|
2732
|
+
const data = {
|
|
2733
|
+
version: 1,
|
|
2734
|
+
blob: this.encrypt(JSON.stringify({ version: 1, report: this.report }))
|
|
2735
|
+
};
|
|
2736
|
+
mkdirSync8(this.baseDir, { recursive: true, mode: 448 });
|
|
2737
|
+
writeFileSync8(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2738
|
+
}
|
|
2739
|
+
};
|
|
2740
|
+
|
|
2091
2741
|
// src/lib/shared-state.ts
|
|
2092
2742
|
var SharedState = class {
|
|
2093
2743
|
spClient;
|
|
@@ -2096,6 +2746,9 @@ var SharedState = class {
|
|
|
2096
2746
|
executionLog;
|
|
2097
2747
|
denialLog;
|
|
2098
2748
|
receiptArchive;
|
|
2749
|
+
/** The one current evidence-backed report the user's AI has written —
|
|
2750
|
+
* see report/report-store.ts. Vault-encrypted, like the stores above. */
|
|
2751
|
+
reportStore;
|
|
2099
2752
|
/** Which tickets this Gatekeeper has executed — the "one execution per ticket" half of exactly-once. */
|
|
2100
2753
|
executionJournal;
|
|
2101
2754
|
/** What THIS gateway submitted for each review-mode proposal it created —
|
|
@@ -2116,6 +2769,7 @@ var SharedState = class {
|
|
|
2116
2769
|
this.executionLog = new ExecutionLog(gateStorePath);
|
|
2117
2770
|
this.denialLog = new DenialLog(gateStorePath);
|
|
2118
2771
|
this.receiptArchive = new ReceiptArchive(gateStorePath);
|
|
2772
|
+
this.reportStore = new ReportStore(gateStorePath);
|
|
2119
2773
|
this.executionJournal = new ExecutionJournal(gateStorePath);
|
|
2120
2774
|
this.proposalSubmissions = new ProposalSubmissionStore(gateStorePath);
|
|
2121
2775
|
this.gatekeeper = new MCPGatekeeper(this.cache);
|
|
@@ -2271,12 +2925,12 @@ function formatConsumptionFull(entries) {
|
|
|
2271
2925
|
|
|
2272
2926
|
// src/lib/context-loader.ts
|
|
2273
2927
|
import { readFileSync as readFileSync12, existsSync as existsSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, renameSync as renameSync3 } from "fs";
|
|
2274
|
-
import { homedir as
|
|
2928
|
+
import { homedir as homedir8 } from "os";
|
|
2275
2929
|
import { join as join11, dirname as dirname9 } from "path";
|
|
2276
|
-
var
|
|
2930
|
+
var DEFAULT_DIR8 = process.env.SUVEREN_DATA_DIR ?? join11(homedir8(), ".suveren");
|
|
2277
2931
|
var CONTEXT_MAX_BYTES = 16 * 1024;
|
|
2278
2932
|
function contextFilePath(dataDir2) {
|
|
2279
|
-
return join11(dataDir2 ??
|
|
2933
|
+
return join11(dataDir2 ?? DEFAULT_DIR8, "context.md");
|
|
2280
2934
|
}
|
|
2281
2935
|
function writeContextFile(content, dataDir2) {
|
|
2282
2936
|
const bytes = Buffer.byteLength(content, "utf-8");
|
|
@@ -2381,8 +3035,8 @@ var FOOTER_RE = /\n*(?:—|--) (?:Sent|Published) by (?:an AI agent|[^\n]+?'s AI
|
|
|
2381
3035
|
function shouldAttachFooter() {
|
|
2382
3036
|
return true;
|
|
2383
3037
|
}
|
|
2384
|
-
function receiptUrl(
|
|
2385
|
-
return `${AS_BASE}/r/${
|
|
3038
|
+
function receiptUrl(receiptId2) {
|
|
3039
|
+
return `${AS_BASE}/r/${receiptId2}`;
|
|
2386
3040
|
}
|
|
2387
3041
|
function verbForProfile(profile) {
|
|
2388
3042
|
return profile === "publish" ? "Published" : "Sent";
|
|
@@ -2390,11 +3044,11 @@ function verbForProfile(profile) {
|
|
|
2390
3044
|
function hapLine() {
|
|
2391
3045
|
return `-- ${OPERATOR_NAME} implements HAP, the open Human Agency Protocol: ${HAP_URL}`;
|
|
2392
3046
|
}
|
|
2393
|
-
function footerText(
|
|
3047
|
+
function footerText(receiptId2, verb, subject) {
|
|
2394
3048
|
const identity = deriveIdentityLine(subject, { operatorName: OPERATOR_NAME }).replace(/^Sent\b/, verb);
|
|
2395
3049
|
return `
|
|
2396
3050
|
|
|
2397
|
-
-- ${identity}. Receipt: ${receiptUrl(
|
|
3051
|
+
-- ${identity}. Receipt: ${receiptUrl(receiptId2)}
|
|
2398
3052
|
|
|
2399
3053
|
${hapLine()}`;
|
|
2400
3054
|
}
|
|
@@ -2413,13 +3067,13 @@ function detectContentField(tool) {
|
|
|
2413
3067
|
function stripFooter(value) {
|
|
2414
3068
|
return value.replace(FOOTER_RE, "").replace(/\n+$/, "");
|
|
2415
3069
|
}
|
|
2416
|
-
function appendVerificationFooter(tool, args,
|
|
3070
|
+
function appendVerificationFooter(tool, args, receiptId2, subject) {
|
|
2417
3071
|
const profile = tool.gating?.profile;
|
|
2418
3072
|
if (!profile || !FOOTER_PROFILES.has(profile)) return args;
|
|
2419
3073
|
const field = detectContentField(tool);
|
|
2420
3074
|
if (!field) return args;
|
|
2421
3075
|
const current = typeof args[field] === "string" ? args[field] : "";
|
|
2422
|
-
return { ...args, [field]: stripFooter(current) + footerText(
|
|
3076
|
+
return { ...args, [field]: stripFooter(current) + footerText(receiptId2, verbForProfile(profile), subject) };
|
|
2423
3077
|
}
|
|
2424
3078
|
|
|
2425
3079
|
// src/lib/content-binding.ts
|
|
@@ -2472,10 +3126,10 @@ function computeContentBinding(profileId, tool, toolArgs, actionType) {
|
|
|
2472
3126
|
boundContent
|
|
2473
3127
|
};
|
|
2474
3128
|
}
|
|
2475
|
-
function attachReceiptId(tool, args,
|
|
3129
|
+
function attachReceiptId(tool, args, receiptId2) {
|
|
2476
3130
|
const schema = tool.inputSchema;
|
|
2477
3131
|
if (!schema?.properties || !("receipt_id" in schema.properties)) return args;
|
|
2478
|
-
return { ...args, receipt_id:
|
|
3132
|
+
return { ...args, receipt_id: receiptId2 };
|
|
2479
3133
|
}
|
|
2480
3134
|
|
|
2481
3135
|
// src/lib/ticket-verify.ts
|
|
@@ -3365,7 +4019,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
3365
4019
|
};
|
|
3366
4020
|
}
|
|
3367
4021
|
}
|
|
3368
|
-
let
|
|
4022
|
+
let receiptId2;
|
|
3369
4023
|
try {
|
|
3370
4024
|
const actionType = declaredActionType;
|
|
3371
4025
|
const binding = computeContentBinding(auth.profileId, tool, args, actionType);
|
|
@@ -3387,7 +4041,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
3387
4041
|
// field by field rather than spread.
|
|
3388
4042
|
...binding ? { contentHash: binding.contentHash, contentBinding: binding.contentBinding } : {}
|
|
3389
4043
|
});
|
|
3390
|
-
|
|
4044
|
+
receiptId2 = typeof receipt?.id === "string" ? receipt.id : void 0;
|
|
3391
4045
|
await verifyTicket(state2.cache, receipt, {
|
|
3392
4046
|
action: tool.namespacedName,
|
|
3393
4047
|
executionContext: { ...execution },
|
|
@@ -3541,12 +4195,12 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.`
|
|
|
3541
4195
|
execution: { ...execution },
|
|
3542
4196
|
timestamp: Math.floor(Date.now() / 1e3)
|
|
3543
4197
|
});
|
|
3544
|
-
let outgoingArgs = shouldAttachFooter() &&
|
|
3545
|
-
if (
|
|
4198
|
+
let outgoingArgs = shouldAttachFooter() && receiptId2 ? appendVerificationFooter(tool, args, receiptId2, auth.subjects?.[0]) : args;
|
|
4199
|
+
if (receiptId2) outgoingArgs = attachReceiptId(tool, outgoingArgs, receiptId2);
|
|
3546
4200
|
outgoingArgs = encodeOutgoingArgs(tool, outgoingArgs);
|
|
3547
|
-
if (
|
|
4201
|
+
if (receiptId2) {
|
|
3548
4202
|
const begun = state2.executionJournal.begin({
|
|
3549
|
-
ticketId:
|
|
4203
|
+
ticketId: receiptId2,
|
|
3550
4204
|
tool: tool.namespacedName,
|
|
3551
4205
|
argsHash: hashToolArgs(args)
|
|
3552
4206
|
});
|
|
@@ -3554,17 +4208,17 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.`
|
|
|
3554
4208
|
return {
|
|
3555
4209
|
content: [{
|
|
3556
4210
|
type: "text",
|
|
3557
|
-
text: `Blocked: ticket ${
|
|
4211
|
+
text: `Blocked: ticket ${receiptId2} was already used to execute ${tool.namespacedName} (state: ${begun.existing.state}). Not running it again.`
|
|
3558
4212
|
}],
|
|
3559
4213
|
isError: true
|
|
3560
4214
|
};
|
|
3561
4215
|
}
|
|
3562
4216
|
try {
|
|
3563
4217
|
const result = await integrationManager2.callTool(tool.integrationId, tool.originalName, outgoingArgs);
|
|
3564
|
-
state2.executionJournal.complete(
|
|
4218
|
+
state2.executionJournal.complete(receiptId2, "done");
|
|
3565
4219
|
return result;
|
|
3566
4220
|
} catch (err) {
|
|
3567
|
-
state2.executionJournal.complete(
|
|
4221
|
+
state2.executionJournal.complete(receiptId2, "failed");
|
|
3568
4222
|
throw err;
|
|
3569
4223
|
}
|
|
3570
4224
|
}
|
|
@@ -4045,7 +4699,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
4045
4699
|
};
|
|
4046
4700
|
}
|
|
4047
4701
|
}
|
|
4048
|
-
let
|
|
4702
|
+
let receiptId2;
|
|
4049
4703
|
let replayed = false;
|
|
4050
4704
|
try {
|
|
4051
4705
|
const binding = computeContentBinding(
|
|
@@ -4078,7 +4732,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
4078
4732
|
// this machine — see the archive call below.
|
|
4079
4733
|
...binding ? { contentHash: binding.contentHash, contentBinding: binding.contentBinding } : {}
|
|
4080
4734
|
});
|
|
4081
|
-
|
|
4735
|
+
receiptId2 = typeof receipt?.id === "string" ? receipt.id : void 0;
|
|
4082
4736
|
replayed = idempotent;
|
|
4083
4737
|
await verifyTicket(state2.cache, receipt, {
|
|
4084
4738
|
action: proposal.tool,
|
|
@@ -4133,14 +4787,14 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
4133
4787
|
isError: true
|
|
4134
4788
|
};
|
|
4135
4789
|
}
|
|
4136
|
-
if (!
|
|
4790
|
+
if (!receiptId2) {
|
|
4137
4791
|
return {
|
|
4138
4792
|
text: `Proposal ${proposal.id}: the ticket carries no id \u2014 refusing to execute an action that could not be recorded.`,
|
|
4139
4793
|
isError: true
|
|
4140
4794
|
};
|
|
4141
4795
|
}
|
|
4142
4796
|
const begun = state2.executionJournal.begin({
|
|
4143
|
-
ticketId:
|
|
4797
|
+
ticketId: receiptId2,
|
|
4144
4798
|
proposalId: proposal.id,
|
|
4145
4799
|
tool: proposal.tool,
|
|
4146
4800
|
argsHash: hashToolArgs(proposal.toolArgs)
|
|
@@ -4150,36 +4804,36 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
4150
4804
|
const when = new Date(prior.startedAt * 1e3).toISOString();
|
|
4151
4805
|
if (prior.state === "done") {
|
|
4152
4806
|
return {
|
|
4153
|
-
text: `Proposal ${proposal.id} was already executed under ticket ${
|
|
4807
|
+
text: `Proposal ${proposal.id} was already executed under ticket ${receiptId2} at ${when}. Not running it again.`
|
|
4154
4808
|
};
|
|
4155
4809
|
}
|
|
4156
4810
|
return {
|
|
4157
|
-
text: `Proposal ${proposal.id}: an execution under ticket ${
|
|
4811
|
+
text: `Proposal ${proposal.id}: an execution under ticket ${receiptId2} started at ${when} (pid ${prior.pid}) and ${prior.state === "failed" ? "reported failure" : "never reported completion"}. Not re-running: the action may already have taken effect. Check the downstream system before deciding.`,
|
|
4158
4812
|
isError: true
|
|
4159
4813
|
};
|
|
4160
4814
|
}
|
|
4161
4815
|
if (replayed) {
|
|
4162
4816
|
console.error(
|
|
4163
|
-
`[Suveren MCP] Proposal ${proposal.id}: ticket ${
|
|
4817
|
+
`[Suveren MCP] Proposal ${proposal.id}: ticket ${receiptId2} was replayed by the AS with no local execution record \u2014 executing once (lost-response recovery).`
|
|
4164
4818
|
);
|
|
4165
4819
|
}
|
|
4166
4820
|
try {
|
|
4167
4821
|
let outgoingArgs = proposal.toolArgs;
|
|
4168
|
-
if (discovered &&
|
|
4822
|
+
if (discovered && receiptId2) {
|
|
4169
4823
|
if (shouldAttachFooter()) {
|
|
4170
|
-
outgoingArgs = appendVerificationFooter(discovered, outgoingArgs,
|
|
4824
|
+
outgoingArgs = appendVerificationFooter(discovered, outgoingArgs, receiptId2, cachedAuth?.subjects?.[0]);
|
|
4171
4825
|
}
|
|
4172
|
-
outgoingArgs = attachReceiptId(discovered, outgoingArgs,
|
|
4826
|
+
outgoingArgs = attachReceiptId(discovered, outgoingArgs, receiptId2);
|
|
4173
4827
|
outgoingArgs = encodeOutgoingArgs(discovered, outgoingArgs);
|
|
4174
4828
|
}
|
|
4175
4829
|
let result;
|
|
4176
4830
|
try {
|
|
4177
4831
|
result = await integrationManager2.callTool(integrationId, toolName, outgoingArgs);
|
|
4178
4832
|
} catch (err) {
|
|
4179
|
-
state2.executionJournal.complete(
|
|
4833
|
+
state2.executionJournal.complete(receiptId2, "failed");
|
|
4180
4834
|
throw err;
|
|
4181
4835
|
}
|
|
4182
|
-
state2.executionJournal.complete(
|
|
4836
|
+
state2.executionJournal.complete(receiptId2, "done");
|
|
4183
4837
|
state2.executionLog.record({
|
|
4184
4838
|
profileId: proposal.profileId,
|
|
4185
4839
|
path: proposal.path,
|
|
@@ -4512,9 +5166,9 @@ function verifyGateContentHashes(content, auth) {
|
|
|
4512
5166
|
|
|
4513
5167
|
// src/lib/integration-registry.ts
|
|
4514
5168
|
import { mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync12 } from "fs";
|
|
4515
|
-
import { homedir as
|
|
5169
|
+
import { homedir as homedir9 } from "os";
|
|
4516
5170
|
import { dirname as dirname10, join as join12 } from "path";
|
|
4517
|
-
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join12(
|
|
5171
|
+
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join12(homedir9(), ".suveren");
|
|
4518
5172
|
var IntegrationRegistry = class {
|
|
4519
5173
|
integrations = /* @__PURE__ */ new Map();
|
|
4520
5174
|
filePath;
|
|
@@ -4591,7 +5245,7 @@ var IntegrationRegistry = class {
|
|
|
4591
5245
|
};
|
|
4592
5246
|
|
|
4593
5247
|
// src/lib/integration-manager.ts
|
|
4594
|
-
import { homedir as
|
|
5248
|
+
import { homedir as homedir10 } from "os";
|
|
4595
5249
|
import { join as join14, delimiter } from "path";
|
|
4596
5250
|
import { existsSync as existsSync14, writeFileSync as writeFileSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync15, rmSync } from "fs";
|
|
4597
5251
|
import { execFile } from "child_process";
|
|
@@ -4730,7 +5384,7 @@ function safeHost(url) {
|
|
|
4730
5384
|
}
|
|
4731
5385
|
|
|
4732
5386
|
// src/lib/integration-manager.ts
|
|
4733
|
-
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join14(
|
|
5387
|
+
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join14(homedir10(), ".suveren");
|
|
4734
5388
|
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join14(DEFAULT_DATA_DIR2, "integrations");
|
|
4735
5389
|
var OFFLINE_MODE = process.env.SUVEREN_OFFLINE === "1";
|
|
4736
5390
|
var installLock = Promise.resolve();
|
|
@@ -4740,6 +5394,9 @@ function withInstallLock(task) {
|
|
|
4740
5394
|
return run;
|
|
4741
5395
|
}
|
|
4742
5396
|
var INTEGRATIONS_BIN = join14(INTEGRATIONS_DIR, "node_modules", ".bin");
|
|
5397
|
+
function getIntegrationsBinDir() {
|
|
5398
|
+
return INTEGRATIONS_BIN;
|
|
5399
|
+
}
|
|
4743
5400
|
function buildPath() {
|
|
4744
5401
|
const base = process.env.PATH ?? "";
|
|
4745
5402
|
return [INTEGRATIONS_BIN, base].join(delimiter);
|
|
@@ -5561,6 +6218,434 @@ var IntegrationManager = class {
|
|
|
5561
6218
|
}
|
|
5562
6219
|
};
|
|
5563
6220
|
|
|
6221
|
+
// src/lib/report/connector-export.ts
|
|
6222
|
+
import { execFile as execFile2 } from "child_process";
|
|
6223
|
+
import { delimiter as delimiter2 } from "path";
|
|
6224
|
+
var DEFAULT_BIN_NAMES = {
|
|
6225
|
+
email: "email-mcp",
|
|
6226
|
+
crm: "crm-mcp",
|
|
6227
|
+
erp: "erp-mcp"
|
|
6228
|
+
};
|
|
6229
|
+
var ConnectorExportError = class extends Error {
|
|
6230
|
+
constructor(message, system, cause) {
|
|
6231
|
+
super(message);
|
|
6232
|
+
this.system = system;
|
|
6233
|
+
this.cause = cause;
|
|
6234
|
+
this.name = "ConnectorExportError";
|
|
6235
|
+
}
|
|
6236
|
+
};
|
|
6237
|
+
function execFileAsync(command, args, options) {
|
|
6238
|
+
return new Promise((resolve3, reject) => {
|
|
6239
|
+
execFile2(command, args, options, (err, stdout, stderr) => {
|
|
6240
|
+
if (err) reject(Object.assign(err, { stdout, stderr }));
|
|
6241
|
+
else resolve3({ stdout, stderr });
|
|
6242
|
+
});
|
|
6243
|
+
});
|
|
6244
|
+
}
|
|
6245
|
+
function createConnectorExportRunner(config) {
|
|
6246
|
+
return async (system) => {
|
|
6247
|
+
const bin = config.binNames?.[system] ?? DEFAULT_BIN_NAMES[system];
|
|
6248
|
+
const env = {
|
|
6249
|
+
...process.env,
|
|
6250
|
+
PATH: [config.integrationsBinDir, process.env.PATH ?? ""].join(delimiter2),
|
|
6251
|
+
HAP_DATA_DIR: config.dataDir
|
|
6252
|
+
};
|
|
6253
|
+
let stdout;
|
|
6254
|
+
try {
|
|
6255
|
+
const result = await execFileAsync(bin, ["export"], {
|
|
6256
|
+
env,
|
|
6257
|
+
timeout: config.timeoutMs ?? 15e3,
|
|
6258
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
6259
|
+
shell: process.platform === "win32"
|
|
6260
|
+
});
|
|
6261
|
+
stdout = result.stdout;
|
|
6262
|
+
} catch (err) {
|
|
6263
|
+
throw new ConnectorExportError(
|
|
6264
|
+
`${bin} export failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
6265
|
+
system,
|
|
6266
|
+
err
|
|
6267
|
+
);
|
|
6268
|
+
}
|
|
6269
|
+
try {
|
|
6270
|
+
return JSON.parse(stdout);
|
|
6271
|
+
} catch (err) {
|
|
6272
|
+
throw new ConnectorExportError(
|
|
6273
|
+
`${bin} export did not print valid JSON on stdout`,
|
|
6274
|
+
system,
|
|
6275
|
+
err
|
|
6276
|
+
);
|
|
6277
|
+
}
|
|
6278
|
+
};
|
|
6279
|
+
}
|
|
6280
|
+
|
|
6281
|
+
// src/lib/report/render-report.ts
|
|
6282
|
+
import { Parser } from "htmlparser2";
|
|
6283
|
+
function findElementSpans(html) {
|
|
6284
|
+
const spans = [];
|
|
6285
|
+
const seenCount = /* @__PURE__ */ new Map();
|
|
6286
|
+
const openStack = [];
|
|
6287
|
+
let parserRef;
|
|
6288
|
+
const parser = new Parser(
|
|
6289
|
+
{
|
|
6290
|
+
onparserinit(p) {
|
|
6291
|
+
parserRef = p;
|
|
6292
|
+
},
|
|
6293
|
+
onopentag(name) {
|
|
6294
|
+
if (!name.startsWith("sv-") || !parserRef) return;
|
|
6295
|
+
const n = seenCount.get(name) ?? 0;
|
|
6296
|
+
seenCount.set(name, n + 1);
|
|
6297
|
+
openStack.push({ tag: name, id: `${name}-${n}`, start: parserRef.startIndex });
|
|
6298
|
+
},
|
|
6299
|
+
onclosetag(name) {
|
|
6300
|
+
if (!name.startsWith("sv-") || !parserRef) return;
|
|
6301
|
+
const idx = [...openStack].reverse().findIndex((o) => o.tag === name);
|
|
6302
|
+
if (idx === -1) return;
|
|
6303
|
+
const realIdx = openStack.length - 1 - idx;
|
|
6304
|
+
const [open] = openStack.splice(realIdx, 1);
|
|
6305
|
+
spans.push({ id: open.id, kind: open.tag, start: open.start, end: parserRef.endIndex + 1 });
|
|
6306
|
+
}
|
|
6307
|
+
},
|
|
6308
|
+
{ decodeEntities: true, recognizeSelfClosing: true }
|
|
6309
|
+
);
|
|
6310
|
+
parser.write(html);
|
|
6311
|
+
parser.end();
|
|
6312
|
+
return spans.sort((a, b) => a.start - b.start);
|
|
6313
|
+
}
|
|
6314
|
+
function escapeHtml(value) {
|
|
6315
|
+
return String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6316
|
+
}
|
|
6317
|
+
function badge(status, okLabel) {
|
|
6318
|
+
if (status === "verified") return `<span class="sv-badge sv-badge-ok">\u2713 ${escapeHtml(okLabel)}</span>`;
|
|
6319
|
+
if (status === "warning") return `<span class="sv-badge sv-badge-warn">\u26A0 verified with a note</span>`;
|
|
6320
|
+
return `<span class="sv-badge sv-badge-bad">\u2717 not verifiable</span>`;
|
|
6321
|
+
}
|
|
6322
|
+
function unverifiableCard(el) {
|
|
6323
|
+
return `<div class="sv-el sv-el-unverifiable" data-sv-id="${escapeHtml(el.id)}"><div class="sv-el-row"><b>Not verifiable</b>${badge("unverifiable", "")}</div><div class="sv-el-reason">${escapeHtml(el.reason ?? "This reference could not be checked.")}</div></div>`;
|
|
6324
|
+
}
|
|
6325
|
+
function renderTicket(el) {
|
|
6326
|
+
if (el.status === "unverifiable" || !el.data) return unverifiableCard(el);
|
|
6327
|
+
const d = el.data;
|
|
6328
|
+
const href = typeof d.checkUrl === "string" ? d.checkUrl : void 0;
|
|
6329
|
+
const actionLabel = typeof d.actionLabel === "string" ? d.actionLabel : formatActionLabel(d.action);
|
|
6330
|
+
const timeLabel = typeof d.timeLabel === "string" ? d.timeLabel : formatDateTime(d.time);
|
|
6331
|
+
const profileLabel = typeof d.profileLabel === "string" ? d.profileLabel : escapeHtml(d.profile ?? "");
|
|
6332
|
+
const links = [
|
|
6333
|
+
href ? `<a href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">Check on suveren.ai \u2197</a>` : ""
|
|
6334
|
+
].filter(Boolean);
|
|
6335
|
+
return `<div class="sv-el sv-el-${el.status}" data-sv-id="${escapeHtml(el.id)}"><div class="sv-el-row"><b>${escapeHtml(actionLabel)}</b>${badge(el.status, "signature valid")}</div><div class="sv-el-row"><span>${escapeHtml(profileLabel)} \xB7 ${escapeHtml(timeLabel)}</span></div>` + (links.length > 0 ? `<div class="sv-el-row"><span>${links.join(" \xB7 ")}</span></div>` : "") + `</div>`;
|
|
6336
|
+
}
|
|
6337
|
+
function renderApproval(el) {
|
|
6338
|
+
if (el.status === "unverifiable" || !el.data) return unverifiableCard(el);
|
|
6339
|
+
const d = el.data;
|
|
6340
|
+
const who = typeof d.whoLabel === "string" ? d.whoLabel : Array.isArray(d.who) ? d.who.join(", ") : "unknown";
|
|
6341
|
+
const asked = typeof d.createdAtLabel === "string" ? d.createdAtLabel : "unknown";
|
|
6342
|
+
const approved = typeof d.decidedAtLabel === "string" ? d.decidedAtLabel : "unknown";
|
|
6343
|
+
const waited = typeof d.waitLabel === "string" ? d.waitLabel : "unknown";
|
|
6344
|
+
return `<div class="sv-el sv-el-${el.status}" data-sv-id="${escapeHtml(el.id)}"><div class="sv-el-row"><b>Approval</b>${badge(el.status, "from ticket archive")}</div><div class="sv-el-row"><span>asked ${escapeHtml(asked)} \xB7 approved ${escapeHtml(approved)} by ${escapeHtml(who)} (${escapeHtml(waited)})</span></div></div>`;
|
|
6345
|
+
}
|
|
6346
|
+
function renderMandate(el) {
|
|
6347
|
+
if (el.status === "unverifiable" || !el.data) return unverifiableCard(el);
|
|
6348
|
+
const d = el.data;
|
|
6349
|
+
const profileLabel = typeof d.profileLabel === "string" ? d.profileLabel : String(d.profile ?? "");
|
|
6350
|
+
const owners = Array.isArray(d.owners) && d.owners.length > 0 ? d.owners.join(", ") : "unknown owner";
|
|
6351
|
+
const limits = Array.isArray(d.limits) && d.limits.length > 0 ? d.limits.join(" \xB7 ") : "";
|
|
6352
|
+
return `<div class="sv-el sv-el-${el.status}" data-sv-id="${escapeHtml(el.id)}"><div class="sv-el-row"><b>${escapeHtml(profileLabel)} \xB7 ${escapeHtml(owners)}</b>${badge(el.status, "matches ticket")}</div>` + (limits ? `<div class="sv-el-row">${escapeHtml(limits)}${d.mode ? ` \xB7 ${escapeHtml(d.mode)}` : ""}</div>` : "") + (d.intent ? `<div class="sv-el-intent">Intent: \u201C${escapeHtml(d.intent)}\u201D</div>` : "") + `</div>`;
|
|
6353
|
+
}
|
|
6354
|
+
var RECORD_FIELD_ORDER = [
|
|
6355
|
+
"subject",
|
|
6356
|
+
"from_email",
|
|
6357
|
+
"from_name",
|
|
6358
|
+
"to_json",
|
|
6359
|
+
"received_at",
|
|
6360
|
+
"number",
|
|
6361
|
+
"status",
|
|
6362
|
+
"net_total",
|
|
6363
|
+
"customer_id",
|
|
6364
|
+
"causingReceiptId",
|
|
6365
|
+
"message"
|
|
6366
|
+
];
|
|
6367
|
+
var RECORD_TIME_FIELDS = /* @__PURE__ */ new Set(["received_at", "created_at", "sent_at"]);
|
|
6368
|
+
function renderRecordField(key, d) {
|
|
6369
|
+
if (key === "net_total") {
|
|
6370
|
+
const currency = typeof d.currency === "string" ? d.currency : void 0;
|
|
6371
|
+
return `<div class="sv-el-row"><span>Value</span><span>${escapeHtml(formatCurrency(d.net_total, currency))}</span></div>`;
|
|
6372
|
+
}
|
|
6373
|
+
if (RECORD_TIME_FIELDS.has(key)) {
|
|
6374
|
+
const t = parseTimestampSeconds(d[key]);
|
|
6375
|
+
const display = t !== void 0 ? formatDateTime(t) : d[key];
|
|
6376
|
+
return `<div class="sv-el-row"><span>${escapeHtml(key)}</span><span>${escapeHtml(display)}</span></div>`;
|
|
6377
|
+
}
|
|
6378
|
+
return `<div class="sv-el-row"><span>${escapeHtml(key)}</span><span>${escapeHtml(d[key])}</span></div>`;
|
|
6379
|
+
}
|
|
6380
|
+
function renderRecord(el) {
|
|
6381
|
+
if (el.status === "unverifiable" || !el.data) return unverifiableCard(el);
|
|
6382
|
+
const d = el.data;
|
|
6383
|
+
const kind = String(d.kind ?? "record");
|
|
6384
|
+
const rows = RECORD_FIELD_ORDER.filter((k) => d[k] !== void 0 && d[k] !== null && d[k] !== "").slice(0, 5).map((k) => renderRecordField(k, d)).join("");
|
|
6385
|
+
return `<div class="sv-el sv-el-${el.status}" data-sv-id="${escapeHtml(el.id)}"><div class="sv-el-row"><b>${escapeHtml(kind)} record</b>${badge(el.status, "in simulator database")}</div>` + rows + `</div>`;
|
|
6386
|
+
}
|
|
6387
|
+
function renderCaseTicketStep(node, isGoal) {
|
|
6388
|
+
const cls = isGoal ? "sv-step sv-step-goal" : "sv-step";
|
|
6389
|
+
const kind = isGoal ? "goal" : "ticket";
|
|
6390
|
+
const inner = `<div class="sv-step-k">${kind}</div><div class="sv-step-t">${escapeHtml(formatActionLabel(node.action))}</div>${formatDateTime(node.time)}`;
|
|
6391
|
+
return `<div class="${cls}">${inner}</div>`;
|
|
6392
|
+
}
|
|
6393
|
+
function renderCaseApprovalStep(approval) {
|
|
6394
|
+
const who = approval.whoLabel ?? (Array.isArray(approval.who) ? approval.who.join(", ") : "unknown");
|
|
6395
|
+
const asked = approval.createdAtLabel ?? "unknown";
|
|
6396
|
+
const decided = approval.decidedAtLabel ?? "unknown";
|
|
6397
|
+
const waited = approval.waitLabel ?? "unknown";
|
|
6398
|
+
return `<div class="sv-step sv-step-approval"><div class="sv-step-k">approval</div><div class="sv-step-t">Approval</div><div class="sv-step-sub">asked ${escapeHtml(asked)} \xB7 approved ${escapeHtml(decided)} by ${escapeHtml(who)} (${escapeHtml(waited)})</div></div>`;
|
|
6399
|
+
}
|
|
6400
|
+
function renderCaseStartStep(start) {
|
|
6401
|
+
let sub = "";
|
|
6402
|
+
if (start.basis === "loaded" && typeof start.emailTime === "number") {
|
|
6403
|
+
sub = `<div class="sv-step-sub">Email dated ${escapeHtml(formatDateTime(start.emailTime))} \xB7 timed from when the test data was loaded</div>`;
|
|
6404
|
+
} else if (start.basis === "load-unknown") {
|
|
6405
|
+
sub = `<div class="sv-step-sub">Email dated ${escapeHtml(formatDateTime(start.emailTime ?? start.time))} \xB7 start time not verifiable</div>`;
|
|
6406
|
+
}
|
|
6407
|
+
const time = start.basis === "load-unknown" ? "" : escapeHtml(formatDateTime(start.time));
|
|
6408
|
+
return `<div class="sv-step sv-step-start"><div class="sv-step-k">start</div><div class="sv-step-t">Email in</div>${time}${sub}</div>`;
|
|
6409
|
+
}
|
|
6410
|
+
function renderCase(el) {
|
|
6411
|
+
if (el.status === "unverifiable" || !el.data) return unverifiableCard(el);
|
|
6412
|
+
const d = el.data;
|
|
6413
|
+
const steps = Array.isArray(d.steps) ? d.steps : [];
|
|
6414
|
+
const goal = d.goal;
|
|
6415
|
+
const approvalsByTicket = /* @__PURE__ */ new Map();
|
|
6416
|
+
if (Array.isArray(d.approvals)) {
|
|
6417
|
+
for (const a of d.approvals) {
|
|
6418
|
+
approvalsByTicket.set(a.ticketId, a);
|
|
6419
|
+
}
|
|
6420
|
+
}
|
|
6421
|
+
const start = d.start;
|
|
6422
|
+
const startStep = start ? { time: start.time, html: renderCaseStartStep(start) } : void 0;
|
|
6423
|
+
const ticketNodes = [
|
|
6424
|
+
...steps.map((s) => ({ node: s, isGoal: false })),
|
|
6425
|
+
...goal ? [{ node: goal, isGoal: true }] : []
|
|
6426
|
+
];
|
|
6427
|
+
const items = [];
|
|
6428
|
+
if (startStep) items.push(startStep);
|
|
6429
|
+
for (const { node, isGoal } of ticketNodes) {
|
|
6430
|
+
const approval = approvalsByTicket.get(node.ticketId);
|
|
6431
|
+
if (approval) {
|
|
6432
|
+
const approvalTime = Math.min(approval.decidedAt ?? approval.createdAt ?? node.time, node.time);
|
|
6433
|
+
items.push({ time: approvalTime, html: renderCaseApprovalStep(approval) });
|
|
6434
|
+
}
|
|
6435
|
+
items.push({ time: node.time, html: renderCaseTicketStep(node, isGoal) });
|
|
6436
|
+
}
|
|
6437
|
+
items.sort((a, b) => a.time - b.time);
|
|
6438
|
+
const stepHtml = items.map((i) => i.html).join("");
|
|
6439
|
+
return `<div class="sv-el sv-el-${el.status}" data-sv-id="${escapeHtml(el.id)}"><div class="sv-el-row"><b>Case ${escapeHtml(d.caseId)} \xB7 ${typeof d.totalDurationSeconds === "number" ? formatDuration(d.totalDurationSeconds) : "time not verifiable"}</b>${badge(el.status, "assembled by gateway")}</div>` + (el.status === "warning" && el.reason ? `<div class="sv-el-warn">${escapeHtml(el.reason)}</div>` : "") + (typeof d.totalDurationSeconds !== "number" ? `<div class="sv-el-warn">${escapeHtml(d.timeUnverifiableReason ?? "Case time not verifiable.")}</div>` : "") + `<div class="sv-tl">${stepHtml}</div></div>`;
|
|
6440
|
+
}
|
|
6441
|
+
function renderMetric(el) {
|
|
6442
|
+
if (el.status === "unverifiable" || !el.data) return unverifiableCard(el);
|
|
6443
|
+
const d = el.data;
|
|
6444
|
+
const kind = String(d.kind ?? "");
|
|
6445
|
+
const label = METRIC_LABELS[kind] ?? kind;
|
|
6446
|
+
const display = formatMetricValue(kind, d.value);
|
|
6447
|
+
return `<div class="sv-el sv-el-${el.status} sv-el-metric" data-sv-id="${escapeHtml(el.id)}"><div class="sv-big">${escapeHtml(display)}</div><div class="sv-el-row"><span>${escapeHtml(label)} (${escapeHtml(d.caseCount ?? 0)} case${d.caseCount === 1 ? "" : "s"})</span>${badge(el.status, "computed by gateway")}</div>` + (el.status === "warning" && el.reason ? `<div class="sv-el-warn">${escapeHtml(el.reason)}</div>` : "") + `</div>`;
|
|
6448
|
+
}
|
|
6449
|
+
function renderElement(el) {
|
|
6450
|
+
switch (el.kind) {
|
|
6451
|
+
case "sv-ticket":
|
|
6452
|
+
return renderTicket(el);
|
|
6453
|
+
case "sv-approval":
|
|
6454
|
+
return renderApproval(el);
|
|
6455
|
+
case "sv-mandate":
|
|
6456
|
+
return renderMandate(el);
|
|
6457
|
+
case "sv-record":
|
|
6458
|
+
return renderRecord(el);
|
|
6459
|
+
case "sv-case":
|
|
6460
|
+
return renderCase(el);
|
|
6461
|
+
case "sv-metric":
|
|
6462
|
+
return renderMetric(el);
|
|
6463
|
+
default:
|
|
6464
|
+
return unverifiableCard(el);
|
|
6465
|
+
}
|
|
6466
|
+
}
|
|
6467
|
+
var DRAWN_ELEMENT_STYLES = `
|
|
6468
|
+
.sv-el { border:1px solid #e5e5e5; border-radius:10px; padding:10px 12px; margin:10px 0; font:13px/1.5 system-ui, sans-serif; background:#fff; }
|
|
6469
|
+
.sv-el-unverifiable { outline:2px solid #b91c1c; outline-offset:2px; }
|
|
6470
|
+
.sv-el-verified, .sv-el-warning { outline:2px solid #15803d; outline-offset:2px; }
|
|
6471
|
+
.sv-el-row { display:flex; justify-content:space-between; gap:10px; flex-wrap:wrap; margin:2px 0; }
|
|
6472
|
+
.sv-el-reason, .sv-el-warn { color:#b45309; font-size:12px; margin-top:4px; }
|
|
6473
|
+
.sv-el-intent { color:#666; font-size:12px; margin-top:4px; }
|
|
6474
|
+
.sv-badge { display:inline-flex; gap:4px; font-size:10.5px; font-weight:600; padding:1px 7px; border-radius:999px; white-space:nowrap; }
|
|
6475
|
+
.sv-badge-ok { color:#15803d; background:#ecfdf3; }
|
|
6476
|
+
.sv-badge-warn { color:#b45309; background:#fff7e6; }
|
|
6477
|
+
.sv-badge-bad { color:#b91c1c; background:#fdecea; }
|
|
6478
|
+
.sv-big { font-size:24px; font-weight:700; }
|
|
6479
|
+
.sv-tl { display:flex; flex-wrap:wrap; gap:10px 16px; padding:6px 0; }
|
|
6480
|
+
.sv-step { min-width:100px; border:1px solid #e5e5e5; border-radius:8px; padding:6px 8px; font-size:11.5px; text-decoration:none; color:inherit; flex-shrink:0; }
|
|
6481
|
+
.sv-step-k { font-size:9.5px; text-transform:uppercase; color:#666; }
|
|
6482
|
+
.sv-step-t { font-weight:600; font-size:12px; }
|
|
6483
|
+
.sv-step-sub { color:#666; white-space:normal; }
|
|
6484
|
+
/* The approval step's text ("asked ... approved ... by ... (duration)") is
|
|
6485
|
+
* longer than a ticket step's \u2014 without a width to wrap against, a
|
|
6486
|
+
* flex item with flex-shrink:0 takes its one-line content width instead of
|
|
6487
|
+
* wrapping, which reads as the text being cut off at the container's visible
|
|
6488
|
+
* edge (polish 2026-10-05, second pass). */
|
|
6489
|
+
.sv-step-approval { max-width:240px; white-space:normal; }
|
|
6490
|
+
.sv-step-start { max-width:240px; white-space:normal; }
|
|
6491
|
+
@media (max-width: 480px) {
|
|
6492
|
+
.sv-tl { flex-direction:column; overflow-x:visible; }
|
|
6493
|
+
.sv-step { min-width:0; }
|
|
6494
|
+
.sv-step-approval { max-width:none; }
|
|
6495
|
+
.sv-step-start { max-width:none; }
|
|
6496
|
+
.sv-el-row { flex-direction:column; gap:2px; }
|
|
6497
|
+
}
|
|
6498
|
+
`;
|
|
6499
|
+
var AI_ANALYSIS_LABEL = "AI analysis \u2014 not verified";
|
|
6500
|
+
var AI_LEGEND_TEXT = "Boxes with a green \u2713 are drawn and checked by the gateway, and each one is listed under \u201CChecked values\u201D. Everything else is the AI's own analysis and is not verified.";
|
|
6501
|
+
function aiLegendBanner() {
|
|
6502
|
+
const box = "display:block !important;visibility:visible !important;opacity:1 !important;position:relative !important;transform:none !important;clip-path:none !important;filter:none !important;z-index:2147483647 !important;margin:0 0 12px 0 !important;padding:8px 12px !important;border:1px solid #d4d4d8 !important;border-left:4px solid #a1a1aa !important;border-radius:8px !important;background:#f4f4f5 !important;color:#3f3f46 !important;font:12.5px/1.45 system-ui, sans-serif !important;text-align:left !important;max-width:none !important;width:auto !important;height:auto !important;";
|
|
6503
|
+
const strong = "display:inline !important;visibility:visible !important;font-weight:700 !important;color:#18181b !important;font-size:inherit !important;";
|
|
6504
|
+
return `<div class="sv-ai-legend" role="note" style="${box}"><b style="${strong}">${escapeHtml(AI_ANALYSIS_LABEL)}.</b> ${escapeHtml(AI_LEGEND_TEXT)}</div>`;
|
|
6505
|
+
}
|
|
6506
|
+
function renderReportHtml(rawHtml, elements) {
|
|
6507
|
+
const html = sanitizeReportHtml(rawHtml);
|
|
6508
|
+
const byId = new Map(elements.map((e) => [e.id, e]));
|
|
6509
|
+
const spans = findElementSpans(html);
|
|
6510
|
+
let out = "";
|
|
6511
|
+
let cursor = 0;
|
|
6512
|
+
for (const span of spans) {
|
|
6513
|
+
out += html.slice(cursor, span.start);
|
|
6514
|
+
const el = byId.get(span.id) ?? { id: span.id, kind: span.kind, attrs: {}, status: "unverifiable", reason: "No verification result for this element." };
|
|
6515
|
+
out += renderElement(el);
|
|
6516
|
+
cursor = span.end;
|
|
6517
|
+
}
|
|
6518
|
+
out += html.slice(cursor);
|
|
6519
|
+
const styleTag = `<style data-sv-drawn-styles="1">${DRAWN_ELEMENT_STYLES}</style>${aiLegendBanner()}`;
|
|
6520
|
+
if (/<body[^>]*>/i.test(out)) {
|
|
6521
|
+
return out.replace(/<body([^>]*)>/i, (m) => `${m}${styleTag}`);
|
|
6522
|
+
}
|
|
6523
|
+
return styleTag + out;
|
|
6524
|
+
}
|
|
6525
|
+
|
|
6526
|
+
// src/lib/report/ticket-details.ts
|
|
6527
|
+
async function buildTicketDetails(archive, ticketIds) {
|
|
6528
|
+
const out = {};
|
|
6529
|
+
for (const id of ticketIds) {
|
|
6530
|
+
const [ticket, approval, mandate] = await Promise.all([
|
|
6531
|
+
resolveTicketElement(archive, id),
|
|
6532
|
+
resolveApprovalElement(archive, id),
|
|
6533
|
+
resolveMandateElement(archive, id)
|
|
6534
|
+
]);
|
|
6535
|
+
out[id] = { ticket, approval, mandate };
|
|
6536
|
+
}
|
|
6537
|
+
return out;
|
|
6538
|
+
}
|
|
6539
|
+
|
|
6540
|
+
// src/lib/report/export-report.ts
|
|
6541
|
+
function receiptId(receipt) {
|
|
6542
|
+
return typeof receipt.id === "string" ? receipt.id : "";
|
|
6543
|
+
}
|
|
6544
|
+
function buildExportBundle(params) {
|
|
6545
|
+
const { stored, archive, authorityServer, gatewayVersion, now = Math.floor(Date.now() / 1e3) } = params;
|
|
6546
|
+
const proof = stored.result.proof;
|
|
6547
|
+
const coverage = stored.result.coverage;
|
|
6548
|
+
const ticketIdSet = /* @__PURE__ */ new Set([...proof.ticketsReferenced, ...coverage.ticketsInPeriod]);
|
|
6549
|
+
const entries = archive.getReceipts().filter((r) => ticketIdSet.has(receiptId(r.receipt)));
|
|
6550
|
+
const tickets = entries.map((r) => r.receipt);
|
|
6551
|
+
const authorizationIds = new Set(entries.map((r) => r.authorizationId));
|
|
6552
|
+
const authorizations = {};
|
|
6553
|
+
for (const a of archive.getAuthorizations()) {
|
|
6554
|
+
if (authorizationIds.has(a.authorizationId)) authorizations[a.authorizationId] = a;
|
|
6555
|
+
}
|
|
6556
|
+
return {
|
|
6557
|
+
format: "suveren-report-export",
|
|
6558
|
+
version: 1,
|
|
6559
|
+
exportedAt: now,
|
|
6560
|
+
gatewayVersion,
|
|
6561
|
+
report: { html: stored.html, savedAt: stored.savedAt, checkedAt: stored.checkedAt },
|
|
6562
|
+
proof,
|
|
6563
|
+
coverage,
|
|
6564
|
+
authorityServer,
|
|
6565
|
+
tickets,
|
|
6566
|
+
authorizations
|
|
6567
|
+
};
|
|
6568
|
+
}
|
|
6569
|
+
function countLine(count, noneText = "none in this report") {
|
|
6570
|
+
return count > 0 ? `${count} \u2713` : noneText;
|
|
6571
|
+
}
|
|
6572
|
+
function renderProofCoveragePanels(proof, coverage) {
|
|
6573
|
+
const casesLine = coverage.emailExportError ? "unknown \u2014 the email simulator could not be read" : `${coverage.coveredCases.length} of ${coverage.loadedCases.length}`;
|
|
6574
|
+
const periodNote = coverage.periodStart === null ? `<p class="sv-export-note">${coverage.emailExportError ? "Test period start unknown \u2014 the email simulator could not be read. All saved tickets were counted." : "Test period start unknown \u2014 all saved tickets were counted."}</p>` : "";
|
|
6575
|
+
const missingParts = [];
|
|
6576
|
+
if (coverage.missingCases.length > 0) missingParts.push(`cases ${coverage.missingCases.map(escapeHtml).join(", ")}`);
|
|
6577
|
+
if (coverage.ticketsNotReferenced.length > 0) missingParts.push(`${coverage.ticketsNotReferenced.length} ticket${coverage.ticketsNotReferenced.length === 1 ? "" : "s"}`);
|
|
6578
|
+
const missingLine = missingParts.length > 0 ? `<p class="sv-export-missing">Not in the report: ${missingParts.join(" \xB7 ")}</p>` : "";
|
|
6579
|
+
const checkedValues = proof.verifiedValues.length > 0 ? `<div class="sv-export-card"><div class="sv-export-card-title">Checked values</div>${proof.verifiedValues.map((v) => `<div class="sv-export-row"><span>${escapeHtml(v.summary)}</span></div>`).join("")}</div>` : "";
|
|
6580
|
+
return `<aside class="sv-export-side"><div class="sv-export-card"><div class="sv-export-card-title">Proof</div><div class="sv-export-row"><span>Tickets referenced</span><b>${proof.ticketsReferenced.length}</b></div><div class="sv-export-row"><span>Signatures valid</span><b>${escapeHtml(countLine(proof.signaturesValid))}</b></div><div class="sv-export-row"><span>Records checked</span><b>${escapeHtml(countLine(proof.recordsChecked))}</b></div><div class="sv-export-row"><span>Not verifiable</span><b>${proof.unverifiableCount}</b></div></div><div class="sv-export-card"><div class="sv-export-card-title">Coverage</div><div class="sv-export-row"><span>Cases</span><b>${casesLine}</b></div><div class="sv-export-row"><span>Tickets in test period</span><b>${coverage.ticketsReferenced.length} of ${coverage.ticketsInPeriod.length}</b></div>` + periodNote + missingLine + `</div>` + checkedValues + `</aside>`;
|
|
6581
|
+
}
|
|
6582
|
+
function ensureHtmlDocument(html) {
|
|
6583
|
+
if (/<html[^>]*>/i.test(html)) return html;
|
|
6584
|
+
return `<!doctype html><html><head><meta charset="utf-8"></head><body>${html}</body></html>`;
|
|
6585
|
+
}
|
|
6586
|
+
function injectIntoHead(html, insert) {
|
|
6587
|
+
if (/<head[^>]*>/i.test(html)) return html.replace(/<head([^>]*)>/i, (m) => `${m}${insert}`);
|
|
6588
|
+
if (/<html[^>]*>/i.test(html)) return html.replace(/<html([^>]*)>/i, (m) => `${m}<head>${insert}</head>`);
|
|
6589
|
+
return `<head>${insert}</head>${html}`;
|
|
6590
|
+
}
|
|
6591
|
+
function injectAfterBodyOpen(html, insert) {
|
|
6592
|
+
if (/<body[^>]*>/i.test(html)) return html.replace(/<body([^>]*)>/i, (m) => `${m}${insert}`);
|
|
6593
|
+
return `${insert}${html}`;
|
|
6594
|
+
}
|
|
6595
|
+
function injectBeforeBodyClose(html, insert) {
|
|
6596
|
+
if (/<\/body>/i.test(html)) return html.replace(/<\/body>/i, `${insert}</body>`);
|
|
6597
|
+
return `${html}${insert}`;
|
|
6598
|
+
}
|
|
6599
|
+
var EXPORT_PAGE_STYLES = `
|
|
6600
|
+
.sv-export-header { font:13px/1.5 system-ui, sans-serif; background:#f6f6f4; border-bottom:1px solid #e5e5e5; padding:14px 20px; }
|
|
6601
|
+
.sv-export-meta { margin:0 0 6px 0; font-weight:600; }
|
|
6602
|
+
.sv-export-howto { margin:0; color:#444; }
|
|
6603
|
+
.sv-export-howto code { background:#eee; border-radius:4px; padding:1px 5px; font-size:12px; }
|
|
6604
|
+
.sv-export-layout { display:flex; gap:20px; align-items:flex-start; padding:20px; font:13px/1.5 system-ui, sans-serif; }
|
|
6605
|
+
.sv-export-main { flex:1 1 auto; min-width:0; }
|
|
6606
|
+
.sv-export-side { flex:0 0 260px; }
|
|
6607
|
+
.sv-export-card { border:1px solid #e5e5e5; border-radius:10px; padding:10px 12px; margin-bottom:12px; background:#fff; }
|
|
6608
|
+
.sv-export-card-title { font-weight:700; margin-bottom:6px; }
|
|
6609
|
+
.sv-export-row { display:flex; justify-content:space-between; gap:10px; margin:2px 0; }
|
|
6610
|
+
.sv-export-note, .sv-export-missing { color:#b45309; font-size:12px; margin:4px 0 0 0; }
|
|
6611
|
+
@media (max-width: 720px) {
|
|
6612
|
+
.sv-export-layout { flex-direction:column; }
|
|
6613
|
+
.sv-export-side { flex:1 1 auto; width:100%; }
|
|
6614
|
+
}
|
|
6615
|
+
`;
|
|
6616
|
+
function formatDateTimeWithYear(value) {
|
|
6617
|
+
const base = formatDateTime(value);
|
|
6618
|
+
const year = new Date(value * 1e3).getFullYear();
|
|
6619
|
+
return base.replace(",", ` ${year},`);
|
|
6620
|
+
}
|
|
6621
|
+
function utcOffsetLabel(value) {
|
|
6622
|
+
const offsetMin = -new Date(value * 1e3).getTimezoneOffset();
|
|
6623
|
+
const sign = offsetMin >= 0 ? "+" : "-";
|
|
6624
|
+
const abs = Math.abs(offsetMin);
|
|
6625
|
+
const hh = Math.floor(abs / 60);
|
|
6626
|
+
const mm = abs % 60;
|
|
6627
|
+
return `UTC${sign}${hh}${mm ? ":" + String(mm).padStart(2, "0") : ""}`;
|
|
6628
|
+
}
|
|
6629
|
+
function buildExportDocument(params) {
|
|
6630
|
+
const { bundle, renderedHtml } = params;
|
|
6631
|
+
const exportedDate = new Date(bundle.exportedAt * 1e3).toISOString().slice(0, 10);
|
|
6632
|
+
const exportedLabel = formatDateTimeWithYear(bundle.exportedAt);
|
|
6633
|
+
const checkedLabel = formatDateTime(bundle.report.checkedAt);
|
|
6634
|
+
const tzLabel = utcOffsetLabel(bundle.exportedAt);
|
|
6635
|
+
const headerHtml = `<div class="sv-export-header"><p class="sv-export-meta">Suveren Gateway ${escapeHtml(bundle.gatewayVersion)} \u2014 exported ${escapeHtml(exportedLabel)} · checked ${escapeHtml(checkedLabel)} (times in ${escapeHtml(tzLabel)})</p><p class="sv-export-howto">How to check this report: each ticket below links to its public record on suveren.ai ("Check on suveren.ai \u2197"). To verify this entire file offline (including every signature), run <code>suveren-gateway verify-report ${escapeHtml(suggestedFilename(bundle))}</code> from a terminal with the Suveren gateway CLI installed.</p><p class="sv-export-legend" role="note" style="display:block !important;visibility:visible !important;opacity:1 !important;margin:6px 0 0 0 !important;color:#3f3f46 !important;font:12.5px/1.45 system-ui, sans-serif !important;"><b style="display:inline !important;visibility:visible !important;color:#18181b !important;">${escapeHtml(AI_ANALYSIS_LABEL)}:</b> ${escapeHtml(AI_LEGEND_TEXT)}</p></div>`;
|
|
6636
|
+
const proofScriptJson = JSON.stringify(bundle).replace(/<\//g, "<\\/");
|
|
6637
|
+
const proofScript = `<script type="application/json" id="suveren-proof">${proofScriptJson}</script>`;
|
|
6638
|
+
let doc = ensureHtmlDocument(renderedHtml);
|
|
6639
|
+
doc = injectIntoHead(doc, `<meta charset="utf-8"><title>Suveren report export \u2014 ${escapeHtml(exportedDate)}</title><style>${DRAWN_ELEMENT_STYLES}${EXPORT_PAGE_STYLES}</style>`);
|
|
6640
|
+
doc = injectAfterBodyOpen(doc, headerHtml + `<div class="sv-export-layout"><div class="sv-export-main">`);
|
|
6641
|
+
doc = injectBeforeBodyClose(doc, `</div>${renderProofCoveragePanels(bundle.proof, bundle.coverage)}</div>${proofScript}`);
|
|
6642
|
+
return doc;
|
|
6643
|
+
}
|
|
6644
|
+
function suggestedFilename(bundle) {
|
|
6645
|
+
const date = new Date(bundle.exportedAt * 1e3).toISOString().slice(0, 10);
|
|
6646
|
+
return `suveren-report-${date}.html`;
|
|
6647
|
+
}
|
|
6648
|
+
|
|
5564
6649
|
// src/lib/profile-loader.ts
|
|
5565
6650
|
import { readFileSync as readFileSync16, existsSync as existsSync15 } from "fs";
|
|
5566
6651
|
import { join as join15, resolve as resolve2 } from "path";
|
|
@@ -5620,6 +6705,251 @@ function builtinText(text) {
|
|
|
5620
6705
|
return { content: [{ type: "text", text }] };
|
|
5621
6706
|
}
|
|
5622
6707
|
|
|
6708
|
+
// src/lib/builtins/report.ts
|
|
6709
|
+
var MAX_REPORT_HTML_BYTES = 512 * 1024;
|
|
6710
|
+
var READ_GATE = { category: "read", boundField: "read_access", requiredValue: "unlimited" };
|
|
6711
|
+
var EMAIL_RECORD_KINDS = ["inbox", "sent", "changes", "refusals"];
|
|
6712
|
+
var ERP_RECORD_KINDS = ["quotes", "orders", "changes", "refusals"];
|
|
6713
|
+
var CRM_RECORD_KINDS = ["contacts", "deals", "tasks", "activities", "changes", "refusals"];
|
|
6714
|
+
function errorText(text) {
|
|
6715
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
6716
|
+
}
|
|
6717
|
+
function listTicketsTool(deps) {
|
|
6718
|
+
return {
|
|
6719
|
+
name: "list_tickets",
|
|
6720
|
+
description: "List tickets (signed executions) from the local receipt archive: id, time, action, action type, profile, authorizationId, the limits used, and whether an approval was archived for it. Optionally filter by since/until (unix seconds). Use the ids with get_ticket for full detail, and with <sv-ticket>, <sv-approval>, <sv-mandate> and <sv-case> in the report.",
|
|
6721
|
+
inputSchema: {
|
|
6722
|
+
type: "object",
|
|
6723
|
+
properties: {
|
|
6724
|
+
since: { type: "number", description: "Only tickets timestamped at or after this unix-seconds value." },
|
|
6725
|
+
until: { type: "number", description: "Only tickets timestamped at or before this unix-seconds value." }
|
|
6726
|
+
}
|
|
6727
|
+
},
|
|
6728
|
+
handler: async (args) => {
|
|
6729
|
+
const since = typeof args.since === "number" ? args.since : void 0;
|
|
6730
|
+
const until = typeof args.until === "number" ? args.until : void 0;
|
|
6731
|
+
const tickets = deps.reportSources.archive.getReceipts().map((entry) => {
|
|
6732
|
+
const r = entry.receipt;
|
|
6733
|
+
const time = typeof r.timestamp === "number" ? r.timestamp : void 0;
|
|
6734
|
+
return {
|
|
6735
|
+
id: typeof r.id === "string" ? r.id : String(r.id ?? ""),
|
|
6736
|
+
time,
|
|
6737
|
+
action: r.action,
|
|
6738
|
+
actionType: r.actionType ?? null,
|
|
6739
|
+
profile: r.profileId,
|
|
6740
|
+
authorizationId: r.authorizationId,
|
|
6741
|
+
limitsUsed: r.limits ?? r.executionContext ?? {},
|
|
6742
|
+
hasApproval: Boolean(entry.proposal)
|
|
6743
|
+
};
|
|
6744
|
+
}).filter((t) => t.id !== "").filter((t) => since === void 0 || t.time !== void 0 && t.time >= since).filter((t) => until === void 0 || t.time !== void 0 && t.time <= until).sort((a, b) => (a.time ?? 0) - (b.time ?? 0));
|
|
6745
|
+
return builtinText(JSON.stringify({ tickets }, null, 2));
|
|
6746
|
+
}
|
|
6747
|
+
};
|
|
6748
|
+
}
|
|
6749
|
+
function getTicketTool(deps) {
|
|
6750
|
+
return {
|
|
6751
|
+
name: "get_ticket",
|
|
6752
|
+
description: 'Full detail for one ticket: the ticket itself (action, time, limits used, public-check link), its archived approval (who approved it, when it was requested and decided, how long it waited), and the mandate it ran under (limits, commitment mode, intent). Each part reports "unverifiable" on its own if it could not be checked \u2014 e.g. an automatic-mode ticket has no approval. Use this to decide what a ticket actually proves before referencing it in the report.',
|
|
6753
|
+
inputSchema: {
|
|
6754
|
+
type: "object",
|
|
6755
|
+
properties: { id: { type: "string", description: "The ticket id, as listed by list_tickets." } },
|
|
6756
|
+
required: ["id"]
|
|
6757
|
+
},
|
|
6758
|
+
handler: async (args) => {
|
|
6759
|
+
const id = typeof args.id === "string" ? args.id.trim() : "";
|
|
6760
|
+
if (!id) return errorText("id is required.");
|
|
6761
|
+
const details = await buildTicketDetails(deps.reportSources.archive, [id]);
|
|
6762
|
+
const detail = details[id];
|
|
6763
|
+
if (!detail || detail.ticket.status !== "verified") {
|
|
6764
|
+
return errorText(detail?.ticket.reason ?? `No ticket "${id}" in the local archive.`);
|
|
6765
|
+
}
|
|
6766
|
+
return builtinText(JSON.stringify({
|
|
6767
|
+
ticket: detail.ticket.data,
|
|
6768
|
+
approval: detail.approval.status === "verified" ? { verified: true, ...detail.approval.data } : { verified: false, reason: detail.approval.reason },
|
|
6769
|
+
mandate: detail.mandate.status === "verified" ? { verified: true, ...detail.mandate.data } : { verified: false, reason: detail.mandate.reason }
|
|
6770
|
+
}, null, 2));
|
|
6771
|
+
}
|
|
6772
|
+
};
|
|
6773
|
+
}
|
|
6774
|
+
function listCasesTool(deps) {
|
|
6775
|
+
return {
|
|
6776
|
+
name: "list_cases",
|
|
6777
|
+
description: "Loaded business cases from the connected email system, plus every sent mail \u2014 so you can define sv-case elements without guessing ids. Returns: when the test data was loaded (testDataLoadedAt); cases (caseId, the starting inbox message id, sender, subject, received time); and sent mails (id, inReplyTo, the receiptId of the ticket that sent it, subject, sentAt) so you can confirm which ticket closed which case. Never includes the team's own reference replies \u2014 those are not part of this report.",
|
|
6778
|
+
inputSchema: { type: "object", properties: {} },
|
|
6779
|
+
handler: async () => {
|
|
6780
|
+
let exported;
|
|
6781
|
+
try {
|
|
6782
|
+
exported = await deps.reportSources.runExport("email");
|
|
6783
|
+
} catch (err) {
|
|
6784
|
+
return errorText(`Could not read email records: ${err instanceof Error ? err.message : String(err)}`);
|
|
6785
|
+
}
|
|
6786
|
+
if (!isEmailExport(exported)) return errorText("Email records were returned in an unexpected shape.");
|
|
6787
|
+
const exp = exported;
|
|
6788
|
+
const cases = exp.inbox.filter((m) => !!m.case_id).map((m) => ({
|
|
6789
|
+
caseId: m.case_id,
|
|
6790
|
+
startMessageId: m.id,
|
|
6791
|
+
from: m.from_email,
|
|
6792
|
+
subject: m.subject,
|
|
6793
|
+
receivedAt: m.received_at
|
|
6794
|
+
}));
|
|
6795
|
+
const sent = exp.sent.map((m) => ({
|
|
6796
|
+
id: m.id,
|
|
6797
|
+
inReplyTo: m.in_reply_to ?? null,
|
|
6798
|
+
receiptId: m.receipt_id ?? null,
|
|
6799
|
+
subject: m.subject,
|
|
6800
|
+
sentAt: m.received_at
|
|
6801
|
+
}));
|
|
6802
|
+
return builtinText(JSON.stringify({
|
|
6803
|
+
testDataLoadedAt: exp.simulation_load?.loaded_at ?? null,
|
|
6804
|
+
cases,
|
|
6805
|
+
sent
|
|
6806
|
+
}, null, 2));
|
|
6807
|
+
}
|
|
6808
|
+
};
|
|
6809
|
+
}
|
|
6810
|
+
function recordKindsFor(system) {
|
|
6811
|
+
if (system === "email") return EMAIL_RECORD_KINDS;
|
|
6812
|
+
if (system === "erp") return ERP_RECORD_KINDS;
|
|
6813
|
+
return CRM_RECORD_KINDS;
|
|
6814
|
+
}
|
|
6815
|
+
function getRecordsTool(deps) {
|
|
6816
|
+
return {
|
|
6817
|
+
name: "get_records",
|
|
6818
|
+
description: `Rows from one connected system (email, erp or crm) that you may reference with <sv-record system="..." ref="...">. Optionally narrow to one table via "kind" (email: inbox/sent/changes/refusals; erp: quotes/orders/changes/refusals; crm: contacts/deals/tasks/activities/changes/refusals) \u2014 omit it to get every table for that system. Never returns the team's own reference replies.`,
|
|
6819
|
+
inputSchema: {
|
|
6820
|
+
type: "object",
|
|
6821
|
+
properties: {
|
|
6822
|
+
system: { type: "string", enum: ["email", "erp", "crm"] },
|
|
6823
|
+
kind: { type: "string", description: "Optional: one table name within that system's records." }
|
|
6824
|
+
},
|
|
6825
|
+
required: ["system"]
|
|
6826
|
+
},
|
|
6827
|
+
handler: async (args) => {
|
|
6828
|
+
const system = args.system;
|
|
6829
|
+
if (system !== "email" && system !== "erp" && system !== "crm") {
|
|
6830
|
+
return errorText('system must be one of "email", "erp", "crm".');
|
|
6831
|
+
}
|
|
6832
|
+
let exported;
|
|
6833
|
+
try {
|
|
6834
|
+
exported = await deps.reportSources.runExport(system);
|
|
6835
|
+
} catch (err) {
|
|
6836
|
+
return errorText(`Could not read ${system} records: ${err instanceof Error ? err.message : String(err)}`);
|
|
6837
|
+
}
|
|
6838
|
+
const ok = system === "email" ? isEmailExport(exported) : system === "erp" ? isErpExport(exported) : isCrmExport(exported);
|
|
6839
|
+
if (!ok) return errorText(`${system} records were returned in an unexpected shape.`);
|
|
6840
|
+
const allowedKinds = recordKindsFor(system);
|
|
6841
|
+
const requestedKind = typeof args.kind === "string" ? args.kind : void 0;
|
|
6842
|
+
if (requestedKind && !allowedKinds.includes(requestedKind)) {
|
|
6843
|
+
return errorText(`kind must be one of ${allowedKinds.join(", ")} for system "${system}".`);
|
|
6844
|
+
}
|
|
6845
|
+
const kinds = requestedKind ? [requestedKind] : allowedKinds;
|
|
6846
|
+
const records = {};
|
|
6847
|
+
const exp = exported;
|
|
6848
|
+
for (const kind of kinds) {
|
|
6849
|
+
records[kind] = exp[kind] ?? [];
|
|
6850
|
+
}
|
|
6851
|
+
return builtinText(JSON.stringify({ system, records }, null, 2));
|
|
6852
|
+
}
|
|
6853
|
+
};
|
|
6854
|
+
}
|
|
6855
|
+
function summarizeWrite(result) {
|
|
6856
|
+
const verified = result.elements.filter((e) => e.status === "verified");
|
|
6857
|
+
const warnings = result.elements.filter((e) => e.status === "warning");
|
|
6858
|
+
const unverifiable = result.elements.filter((e) => e.status === "unverifiable");
|
|
6859
|
+
const lines = [];
|
|
6860
|
+
lines.push(
|
|
6861
|
+
`Report stored. ${verified.length} element(s) verified, ${warnings.length} warning(s), ${unverifiable.length} not verifiable.`
|
|
6862
|
+
);
|
|
6863
|
+
if (warnings.length > 0) {
|
|
6864
|
+
lines.push("Warnings:");
|
|
6865
|
+
for (const w of warnings) lines.push(` - ${w.kind} (${w.id}): ${w.reason ?? "unspecified"}`);
|
|
6866
|
+
}
|
|
6867
|
+
if (unverifiable.length > 0) {
|
|
6868
|
+
lines.push("Not verifiable \u2014 fix these references and write again:");
|
|
6869
|
+
for (const u of unverifiable) lines.push(` - ${u.kind} (${u.id}): ${u.reason ?? "unspecified"}`);
|
|
6870
|
+
}
|
|
6871
|
+
const cov = result.coverage;
|
|
6872
|
+
if (cov.emailExportError) {
|
|
6873
|
+
lines.push(`Coverage could not be checked: ${cov.emailExportError}`);
|
|
6874
|
+
} else {
|
|
6875
|
+
lines.push(`Coverage: ${cov.coveredCases.length} of ${cov.loadedCases.length} loaded case(s) covered.`);
|
|
6876
|
+
if (cov.missingCases.length > 0) lines.push(`Missing cases (not defined with sv-case): ${cov.missingCases.join(", ")}`);
|
|
6877
|
+
if (cov.ticketsNotReferenced.length > 0) {
|
|
6878
|
+
lines.push(`Tickets from the test period not referenced anywhere in the report: ${cov.ticketsNotReferenced.join(", ")}`);
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
return lines.join("\n");
|
|
6882
|
+
}
|
|
6883
|
+
function writeReportRefusal(args) {
|
|
6884
|
+
const html = typeof args.html === "string" ? args.html : "";
|
|
6885
|
+
if (!html.trim()) return "`html` must be a non-empty string.";
|
|
6886
|
+
const bytes = Buffer.byteLength(html, "utf8");
|
|
6887
|
+
if (bytes > MAX_REPORT_HTML_BYTES) {
|
|
6888
|
+
return `the report is ${bytes} bytes, over the ${MAX_REPORT_HTML_BYTES}-byte limit. Shorten it (the gateway draws the verifiable elements for you \u2014 you do not need to inline their data) and write again.`;
|
|
6889
|
+
}
|
|
6890
|
+
return void 0;
|
|
6891
|
+
}
|
|
6892
|
+
function writeReportTool(deps) {
|
|
6893
|
+
return {
|
|
6894
|
+
name: "write_report",
|
|
6895
|
+
// The brief IS the description — this is how it reaches the AI (R3/R4 plan,
|
|
6896
|
+
// the same pattern mail.json's load_simulation uses for its package guide).
|
|
6897
|
+
description: REPORT_BRIEF,
|
|
6898
|
+
inputSchema: {
|
|
6899
|
+
type: "object",
|
|
6900
|
+
properties: {
|
|
6901
|
+
html: { type: "string", description: "The full report HTML \u2014 see the tool description for how to write it." },
|
|
6902
|
+
receipt_id: { type: "string" }
|
|
6903
|
+
},
|
|
6904
|
+
required: ["html"]
|
|
6905
|
+
},
|
|
6906
|
+
// Empty/oversize html is refused here, BEFORE the gate requests a ticket
|
|
6907
|
+
// (precheckBuiltin) — a refused write must not consume report_daily_max.
|
|
6908
|
+
validate: writeReportRefusal,
|
|
6909
|
+
handler: async (args) => {
|
|
6910
|
+
const refusal = writeReportRefusal(args);
|
|
6911
|
+
if (refusal) return errorText(`Refused: ${refusal}`);
|
|
6912
|
+
const html = args.html;
|
|
6913
|
+
if (deps.state.reportStore.isLocked()) {
|
|
6914
|
+
return errorText("The report store is locked (the vault is not unlocked) \u2014 cannot save a report right now.");
|
|
6915
|
+
}
|
|
6916
|
+
try {
|
|
6917
|
+
const stored = await deps.state.reportStore.saveReport(html, deps.reportSources);
|
|
6918
|
+
return builtinText(summarizeWrite(stored.result));
|
|
6919
|
+
} catch (err) {
|
|
6920
|
+
return errorText(`Could not verify and store the report: ${err instanceof Error ? err.message : String(err)}`);
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
};
|
|
6924
|
+
}
|
|
6925
|
+
var reportBuiltin = (deps) => ({
|
|
6926
|
+
id: "report",
|
|
6927
|
+
name: "Report",
|
|
6928
|
+
// Short name — matches the real registration in profile-loader.ts (full id
|
|
6929
|
+
// AND short name both registered); profileMatches() accepts either.
|
|
6930
|
+
profile: "reporting",
|
|
6931
|
+
// Touches no real system (reads the local archive + each simulator's own
|
|
6932
|
+
// read-only export CLI; writes only the local, vault-encrypted report file)
|
|
6933
|
+
// — safe during a simulation-mode test.
|
|
6934
|
+
simulation: true,
|
|
6935
|
+
toolGating: {
|
|
6936
|
+
overrides: {
|
|
6937
|
+
list_tickets: READ_GATE,
|
|
6938
|
+
get_ticket: READ_GATE,
|
|
6939
|
+
list_cases: READ_GATE,
|
|
6940
|
+
get_records: READ_GATE,
|
|
6941
|
+
write_report: { executionMapping: {}, staticExecution: { action_type: "report" } }
|
|
6942
|
+
}
|
|
6943
|
+
},
|
|
6944
|
+
tools: [
|
|
6945
|
+
listTicketsTool(deps),
|
|
6946
|
+
getTicketTool(deps),
|
|
6947
|
+
listCasesTool(deps),
|
|
6948
|
+
getRecordsTool(deps),
|
|
6949
|
+
writeReportTool(deps)
|
|
6950
|
+
]
|
|
6951
|
+
});
|
|
6952
|
+
|
|
5623
6953
|
// src/lib/cp-mandate.ts
|
|
5624
6954
|
var CP_PORT2 = process.env.SUVEREN_CP_PORT ?? "3402";
|
|
5625
6955
|
var CP_BASE2 = process.env.SUVEREN_CP_INTERNAL_URL ?? `http://127.0.0.1:${CP_PORT2}`;
|
|
@@ -5841,7 +7171,7 @@ ${guide.body.trim()}`);
|
|
|
5841
7171
|
inputSchema: {
|
|
5842
7172
|
type: "object",
|
|
5843
7173
|
properties: {
|
|
5844
|
-
team: { type: "string", description:
|
|
7174
|
+
team: { type: "string", description: 'Team name or id, or "personal". Omit to use the workspace of your Delegation mandate.' },
|
|
5845
7175
|
profile: { type: "string", description: 'Profile, e.g. "sales" (newest version) or a full profile id.' },
|
|
5846
7176
|
limits: { type: "object", description: "The limits (bounds) by field name, as the profile defines them." },
|
|
5847
7177
|
scope: { type: "object", description: "The scope (context) by field name, as the profile defines it." },
|
|
@@ -5886,7 +7216,7 @@ function mandateRequest(args) {
|
|
|
5886
7216
|
}
|
|
5887
7217
|
|
|
5888
7218
|
// src/lib/builtins/index.ts
|
|
5889
|
-
var BUILTIN_FACTORIES = [setupBuiltin];
|
|
7219
|
+
var BUILTIN_FACTORIES = [setupBuiltin, reportBuiltin];
|
|
5890
7220
|
function registerBuiltins(deps, factories = BUILTIN_FACTORIES) {
|
|
5891
7221
|
const registered = [];
|
|
5892
7222
|
for (const factory of factories) {
|
|
@@ -5985,7 +7315,7 @@ function isAlive(pid) {
|
|
|
5985
7315
|
}
|
|
5986
7316
|
|
|
5987
7317
|
// bin/http.ts
|
|
5988
|
-
import { homedir as
|
|
7318
|
+
import { homedir as homedir11 } from "os";
|
|
5989
7319
|
import { join as join19 } from "path";
|
|
5990
7320
|
|
|
5991
7321
|
// src/lib/mcp-as-tracker.ts
|
|
@@ -6011,12 +7341,16 @@ function writeMcpPairedAsUrl(dataDir2, asUrl) {
|
|
|
6011
7341
|
}
|
|
6012
7342
|
|
|
6013
7343
|
// bin/http.ts
|
|
6014
|
-
var dataDir = process.env.SUVEREN_DATA_DIR ?? join19(
|
|
7344
|
+
var dataDir = process.env.SUVEREN_DATA_DIR ?? join19(homedir11(), ".suveren");
|
|
6015
7345
|
var spUrl = resolveAsUrl(dataDir);
|
|
6016
7346
|
validatePinTlsForUrl(resolvePinTls(dataDir), spUrl);
|
|
6017
7347
|
var port = parseInt(process.env.SUVEREN_MCP_PORT ?? "3430", 10);
|
|
6018
7348
|
setAsBaseUrl(spUrl);
|
|
6019
7349
|
var state = new SharedState(spUrl, void 0, dataDir);
|
|
7350
|
+
var reportSources = {
|
|
7351
|
+
archive: state.receiptArchive,
|
|
7352
|
+
runExport: createConnectorExportRunner({ integrationsBinDir: getIntegrationsBinDir(), dataDir })
|
|
7353
|
+
};
|
|
6020
7354
|
(function repairIfAsUrlChanged() {
|
|
6021
7355
|
const lastKnown = readMcpPairedAsUrl(dataDir);
|
|
6022
7356
|
if (lastKnown !== null && lastKnown !== spUrl) {
|
|
@@ -6092,7 +7426,8 @@ app.post("/internal/configure", internalOnly, (req, res) => {
|
|
|
6092
7426
|
state.gateStore.setVaultKey(key);
|
|
6093
7427
|
state.denialLog.setVaultKey(key);
|
|
6094
7428
|
state.receiptArchive.setVaultKey(key);
|
|
6095
|
-
|
|
7429
|
+
state.reportStore.setVaultKey(key);
|
|
7430
|
+
console.error("[Suveren MCP] Vault key configured \u2014 gate store + denial log + receipt archive + report store encryption active");
|
|
6096
7431
|
}
|
|
6097
7432
|
if (apiKey) {
|
|
6098
7433
|
state.spClient.setApiKey(apiKey);
|
|
@@ -6432,6 +7767,107 @@ app.get("/internal/evidence", internalOnly, (_req, res) => {
|
|
|
6432
7767
|
gates: state.gateStore.getAll()
|
|
6433
7768
|
});
|
|
6434
7769
|
});
|
|
7770
|
+
async function reportResponsePayload(stored) {
|
|
7771
|
+
const ticketDetails = await buildTicketDetails(state.receiptArchive, stored.result.proof.ticketsReferenced);
|
|
7772
|
+
return {
|
|
7773
|
+
savedAt: stored.savedAt,
|
|
7774
|
+
checkedAt: stored.checkedAt,
|
|
7775
|
+
renderedHtml: renderReportHtml(stored.result.html, stored.result.elements),
|
|
7776
|
+
proof: stored.result.proof,
|
|
7777
|
+
coverage: stored.result.coverage,
|
|
7778
|
+
elements: stored.result.elements,
|
|
7779
|
+
ticketDetails
|
|
7780
|
+
};
|
|
7781
|
+
}
|
|
7782
|
+
app.get("/internal/report", internalOnly, async (_req, res) => {
|
|
7783
|
+
if (state.reportStore.isLocked()) {
|
|
7784
|
+
res.status(503).json({ error: "Vault locked \u2014 the report is encrypted until sign-in." });
|
|
7785
|
+
return;
|
|
7786
|
+
}
|
|
7787
|
+
const stored = state.reportStore.getReport();
|
|
7788
|
+
if (!stored) {
|
|
7789
|
+
res.json({ report: null });
|
|
7790
|
+
return;
|
|
7791
|
+
}
|
|
7792
|
+
res.json({ report: await reportResponsePayload(stored) });
|
|
7793
|
+
});
|
|
7794
|
+
app.post("/internal/report", internalOnly, async (req, res) => {
|
|
7795
|
+
if (state.reportStore.isLocked()) {
|
|
7796
|
+
res.status(503).json({ error: "Vault locked \u2014 cannot save a report until sign-in." });
|
|
7797
|
+
return;
|
|
7798
|
+
}
|
|
7799
|
+
const html = req.body?.html;
|
|
7800
|
+
if (typeof html !== "string" || !html.trim()) {
|
|
7801
|
+
res.status(400).json({ error: "Missing required field: html (non-empty string)" });
|
|
7802
|
+
return;
|
|
7803
|
+
}
|
|
7804
|
+
try {
|
|
7805
|
+
const stored = await state.reportStore.saveReport(html, reportSources);
|
|
7806
|
+
res.json({ report: await reportResponsePayload(stored) });
|
|
7807
|
+
} catch (err) {
|
|
7808
|
+
console.error("[Suveren MCP] /internal/report save failed:", err);
|
|
7809
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "Report verification failed" });
|
|
7810
|
+
}
|
|
7811
|
+
});
|
|
7812
|
+
app.post("/internal/report/recheck", internalOnly, async (_req, res) => {
|
|
7813
|
+
if (state.reportStore.isLocked()) {
|
|
7814
|
+
res.status(503).json({ error: "Vault locked \u2014 cannot recheck until sign-in." });
|
|
7815
|
+
return;
|
|
7816
|
+
}
|
|
7817
|
+
try {
|
|
7818
|
+
const stored = await state.reportStore.recheck(reportSources);
|
|
7819
|
+
if (!stored) {
|
|
7820
|
+
res.json({ report: null });
|
|
7821
|
+
return;
|
|
7822
|
+
}
|
|
7823
|
+
res.json({ report: await reportResponsePayload(stored) });
|
|
7824
|
+
} catch (err) {
|
|
7825
|
+
console.error("[Suveren MCP] /internal/report/recheck failed:", err);
|
|
7826
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "Recheck failed" });
|
|
7827
|
+
}
|
|
7828
|
+
});
|
|
7829
|
+
function resolveExportAuthorityServer() {
|
|
7830
|
+
const pin = readPairing(dataDir);
|
|
7831
|
+
if (pin) return { url: pin.asUrl, publicKeyHex: pin.publicKeyHex };
|
|
7832
|
+
const withKey = state.receiptArchive.getReceipts().filter((r) => !!r.asPublicKey).sort((a, b) => b.archivedAt - a.archivedAt);
|
|
7833
|
+
if (withKey.length > 0) return { url: withKey[0].asUrl, publicKeyHex: withKey[0].asPublicKey };
|
|
7834
|
+
return { url: spUrl, publicKeyHex: "" };
|
|
7835
|
+
}
|
|
7836
|
+
app.get("/internal/report/export", internalOnly, async (req, res) => {
|
|
7837
|
+
if (state.reportStore.isLocked() || state.receiptArchive.isLocked()) {
|
|
7838
|
+
res.status(503).json({ error: "Vault locked \u2014 the report is encrypted until sign-in." });
|
|
7839
|
+
return;
|
|
7840
|
+
}
|
|
7841
|
+
let stored;
|
|
7842
|
+
try {
|
|
7843
|
+
stored = await state.reportStore.recheck(reportSources);
|
|
7844
|
+
} catch (err) {
|
|
7845
|
+
console.error("[Suveren MCP] /internal/report/export recheck failed:", err);
|
|
7846
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "Recheck failed" });
|
|
7847
|
+
return;
|
|
7848
|
+
}
|
|
7849
|
+
if (!stored) {
|
|
7850
|
+
res.status(404).json({ error: "No report to export yet \u2014 the AI writes it with the Reporting mandate." });
|
|
7851
|
+
return;
|
|
7852
|
+
}
|
|
7853
|
+
const authorityServer = resolveExportAuthorityServer();
|
|
7854
|
+
if (!authorityServer.publicKeyHex) {
|
|
7855
|
+
res.status(503).json({
|
|
7856
|
+
error: "No Authority Server key is available yet to anchor this export \u2014 sign in once, or execute at least one gated action, before exporting."
|
|
7857
|
+
});
|
|
7858
|
+
return;
|
|
7859
|
+
}
|
|
7860
|
+
const gatewayVersion = typeof req.query.gatewayVersion === "string" ? req.query.gatewayVersion : "unknown";
|
|
7861
|
+
const bundle = buildExportBundle({
|
|
7862
|
+
stored,
|
|
7863
|
+
archive: state.receiptArchive,
|
|
7864
|
+
authorityServer,
|
|
7865
|
+
gatewayVersion
|
|
7866
|
+
});
|
|
7867
|
+
const renderedHtml = renderReportHtml(stored.result.html, stored.result.elements);
|
|
7868
|
+
const document = buildExportDocument({ bundle, renderedHtml });
|
|
7869
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8").setHeader("Content-Disposition", `attachment; filename="${suggestedFilename(bundle)}"`).setHeader("Cache-Control", "no-store").send(document);
|
|
7870
|
+
});
|
|
6435
7871
|
app.get("/internal/brief", internalOnly, (_req, res) => {
|
|
6436
7872
|
try {
|
|
6437
7873
|
const enriched = state.getEnrichedAuthorizations();
|
|
@@ -6502,7 +7938,7 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
6502
7938
|
console.error(`[Suveren MCP] SP server: ${spUrl}`);
|
|
6503
7939
|
profilesLoaded = loadProfiles();
|
|
6504
7940
|
loadManifests();
|
|
6505
|
-
registerBuiltins({ state, integrationManager });
|
|
7941
|
+
registerBuiltins({ state, integrationManager, reportSources });
|
|
6506
7942
|
if (process.env.SUVEREN_DISABLE_AUTO_INTEGRATIONS !== "1" && integrationRegistry.getEnabled().length === 0) {
|
|
6507
7943
|
const personalManifests = getAllManifests().filter((m) => m.personalDefault);
|
|
6508
7944
|
for (const manifest of personalManifests) {
|
|
@@ -6545,7 +7981,7 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
6545
7981
|
(proposal) => executeCommitted(proposal, state, integrationManager)
|
|
6546
7982
|
);
|
|
6547
7983
|
installCommittedExecutor(committedExecutor);
|
|
6548
|
-
const dataDir2 = process.env.SUVEREN_DATA_DIR ?? join19(
|
|
7984
|
+
const dataDir2 = process.env.SUVEREN_DATA_DIR ?? join19(homedir11(), ".suveren");
|
|
6549
7985
|
const executorLock = new ExecutorLock(dataDir2);
|
|
6550
7986
|
const lock = executorLock.acquire();
|
|
6551
7987
|
if (!lock.held) {
|