@suveren/gateway 0.18.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-JUFH4SKS.mjs +639 -0
- package/dist/mcp-server/http.mjs +1592 -190
- package/dist/mcp-server/report-verify-cli.mjs +171 -0
- package/dist/ui/assets/index-78vuGrVE.js +111 -0
- package/dist/ui/assets/{index-y1OFeYnS.css → index-Dfh38n-b.css} +1 -1
- 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,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
METRIC_LABELS,
|
|
4
|
+
fingerprintOf,
|
|
5
|
+
formatActionLabel,
|
|
6
|
+
formatCurrency,
|
|
7
|
+
formatDateTime,
|
|
8
|
+
formatDuration,
|
|
9
|
+
formatMetricValue,
|
|
10
|
+
parseCaseAttrs,
|
|
11
|
+
parseElements,
|
|
12
|
+
parseTimestampSeconds,
|
|
13
|
+
profileShortLabel,
|
|
14
|
+
readPairing,
|
|
15
|
+
resolveApprovalElement,
|
|
16
|
+
resolveCaseElement,
|
|
17
|
+
resolveMandateElement,
|
|
18
|
+
resolveTicketElement
|
|
19
|
+
} from "./chunk-JUFH4SKS.mjs";
|
|
2
20
|
|
|
3
21
|
// bin/http.ts
|
|
4
22
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -112,42 +130,13 @@ function clientVersionHeaders() {
|
|
|
112
130
|
};
|
|
113
131
|
}
|
|
114
132
|
|
|
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
133
|
// src/lib/as-config.ts
|
|
145
|
-
import { existsSync as
|
|
146
|
-
import { dirname
|
|
134
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
135
|
+
import { dirname, join as join3 } from "path";
|
|
147
136
|
|
|
148
137
|
// src/lib/policy.ts
|
|
149
|
-
import { existsSync as
|
|
150
|
-
import { join as
|
|
138
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
139
|
+
import { join as join2 } from "path";
|
|
151
140
|
import { spawnSync } from "child_process";
|
|
152
141
|
var POLICY_KEYS = ["AsUrl", "CaFile", "PinTls", "Proxy", "NoProxy", "Simulation", "InstallMethod"];
|
|
153
142
|
var DEFAULT_REGISTRY_BASE_KEY = "SOFTWARE\\Policies\\Suveren\\Gateway";
|
|
@@ -182,7 +171,7 @@ function validateAsUrl(candidate) {
|
|
|
182
171
|
function validateCaFile(candidate) {
|
|
183
172
|
const trimmed = candidate.trim();
|
|
184
173
|
if (!trimmed) return { ok: false, error: "The CA file path is empty." };
|
|
185
|
-
if (!
|
|
174
|
+
if (!existsSync2(trimmed)) return { ok: false, error: `No file at "${trimmed}".` };
|
|
186
175
|
return { ok: true, path: trimmed };
|
|
187
176
|
}
|
|
188
177
|
function validateProxyUrl(candidate) {
|
|
@@ -248,7 +237,7 @@ function policyFilePath() {
|
|
|
248
237
|
if (process.env.SUVEREN_POLICY_FILE) return process.env.SUVEREN_POLICY_FILE;
|
|
249
238
|
if (process.platform === "win32") {
|
|
250
239
|
const programData = process.env.ProgramData || process.env.PROGRAMDATA || "C:\\ProgramData";
|
|
251
|
-
return
|
|
240
|
+
return join2(programData, "Suveren", "gateway-policy.json");
|
|
252
241
|
}
|
|
253
242
|
if (process.platform === "darwin") {
|
|
254
243
|
return "/Library/Application Support/Suveren/gateway-policy.json";
|
|
@@ -258,10 +247,10 @@ function policyFilePath() {
|
|
|
258
247
|
function readPolicyFile() {
|
|
259
248
|
const path = policyFilePath();
|
|
260
249
|
const source = `the policy file (${path})`;
|
|
261
|
-
if (!
|
|
250
|
+
if (!existsSync2(path)) return { values: {}, source };
|
|
262
251
|
let data;
|
|
263
252
|
try {
|
|
264
|
-
data = JSON.parse(
|
|
253
|
+
data = JSON.parse(readFileSync2(path, "utf8"));
|
|
265
254
|
} catch (err) {
|
|
266
255
|
throw new Error(`Could not parse policy file ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
267
256
|
}
|
|
@@ -365,14 +354,14 @@ function readPolicy() {
|
|
|
365
354
|
// src/lib/as-config.ts
|
|
366
355
|
var DEFAULT_AS_URL = "https://www.suveren.ai";
|
|
367
356
|
function configPath(dataDir2) {
|
|
368
|
-
return
|
|
357
|
+
return join3(dataDir2, "config.json");
|
|
369
358
|
}
|
|
370
359
|
function readAsConfigStrict(dataDir2) {
|
|
371
360
|
const path = configPath(dataDir2);
|
|
372
|
-
if (!
|
|
361
|
+
if (!existsSync3(path)) return {};
|
|
373
362
|
let data;
|
|
374
363
|
try {
|
|
375
|
-
data = JSON.parse(
|
|
364
|
+
data = JSON.parse(readFileSync3(path, "utf-8"));
|
|
376
365
|
} catch (err) {
|
|
377
366
|
throw new Error(
|
|
378
367
|
`Could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -387,9 +376,9 @@ function readAsConfigStrict(dataDir2) {
|
|
|
387
376
|
}
|
|
388
377
|
function readAsConfig(dataDir2) {
|
|
389
378
|
const path = configPath(dataDir2);
|
|
390
|
-
if (!
|
|
379
|
+
if (!existsSync3(path)) return {};
|
|
391
380
|
try {
|
|
392
|
-
const data = JSON.parse(
|
|
381
|
+
const data = JSON.parse(readFileSync3(path, "utf-8"));
|
|
393
382
|
const out = {};
|
|
394
383
|
if (typeof data.asUrl === "string") out.asUrl = data.asUrl;
|
|
395
384
|
if (typeof data.caFile === "string") out.caFile = data.caFile;
|
|
@@ -461,10 +450,10 @@ function validatePinTlsForUrl(pinTls, asUrl) {
|
|
|
461
450
|
}
|
|
462
451
|
|
|
463
452
|
// src/lib/as-tls-pin.ts
|
|
464
|
-
import { createHash
|
|
453
|
+
import { createHash } from "crypto";
|
|
465
454
|
import { checkServerIdentity as defaultCheckServerIdentity, rootCertificates } from "tls";
|
|
466
455
|
import { Agent as HttpsAgent2 } from "https";
|
|
467
|
-
import { readFileSync as
|
|
456
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
468
457
|
import { Agent, ProxyAgent, fetch as undiciFetch } from "undici";
|
|
469
458
|
|
|
470
459
|
// src/lib/proxy-env.ts
|
|
@@ -514,7 +503,7 @@ function effectiveCa() {
|
|
|
514
503
|
const path = process.env.NODE_EXTRA_CA_CERTS;
|
|
515
504
|
if (!path) return void 0;
|
|
516
505
|
try {
|
|
517
|
-
return [...rootCertificates,
|
|
506
|
+
return [...rootCertificates, readFileSync4(path, "utf-8")];
|
|
518
507
|
} catch {
|
|
519
508
|
return void 0;
|
|
520
509
|
}
|
|
@@ -542,7 +531,7 @@ function spkiSha256Hex(cert) {
|
|
|
542
531
|
if (!cert.pubkey) {
|
|
543
532
|
throw new Error("peer certificate carries no pubkey (expected the detailed certificate object)");
|
|
544
533
|
}
|
|
545
|
-
return
|
|
534
|
+
return createHash("sha256").update(cert.pubkey).digest("hex");
|
|
546
535
|
}
|
|
547
536
|
function formatPinFingerprint(hex) {
|
|
548
537
|
const upper = hex.toUpperCase();
|
|
@@ -1148,18 +1137,18 @@ var AttestationCache = class {
|
|
|
1148
1137
|
};
|
|
1149
1138
|
|
|
1150
1139
|
// src/lib/gate-store.ts
|
|
1151
|
-
import { mkdirSync as
|
|
1140
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync4, unlinkSync } from "fs";
|
|
1152
1141
|
import { homedir } from "os";
|
|
1153
|
-
import { dirname as
|
|
1142
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
1154
1143
|
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
1155
|
-
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
1144
|
+
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ?? join4(homedir(), ".suveren");
|
|
1156
1145
|
var GateStore = class {
|
|
1157
1146
|
entries = /* @__PURE__ */ new Map();
|
|
1158
1147
|
baseDir;
|
|
1159
1148
|
vaultKey = null;
|
|
1160
1149
|
constructor(filePath) {
|
|
1161
1150
|
if (filePath && filePath.endsWith(".json")) {
|
|
1162
|
-
this.baseDir =
|
|
1151
|
+
this.baseDir = dirname2(filePath);
|
|
1163
1152
|
} else {
|
|
1164
1153
|
this.baseDir = filePath ?? DEFAULT_DIR;
|
|
1165
1154
|
}
|
|
@@ -1170,13 +1159,13 @@ var GateStore = class {
|
|
|
1170
1159
|
*/
|
|
1171
1160
|
setVaultKey(key) {
|
|
1172
1161
|
this.vaultKey = key;
|
|
1173
|
-
if (
|
|
1162
|
+
if (existsSync4(this.encryptedFilePath)) {
|
|
1174
1163
|
this.loadEncrypted();
|
|
1175
1164
|
} else if (this.entries.size > 0) {
|
|
1176
1165
|
this.persistEncrypted();
|
|
1177
|
-
if (
|
|
1166
|
+
if (existsSync4(this.plaintextFilePath)) {
|
|
1178
1167
|
try {
|
|
1179
|
-
|
|
1168
|
+
unlinkSync(this.plaintextFilePath);
|
|
1180
1169
|
} catch {
|
|
1181
1170
|
}
|
|
1182
1171
|
}
|
|
@@ -1212,11 +1201,11 @@ var GateStore = class {
|
|
|
1212
1201
|
clearAll() {
|
|
1213
1202
|
this.entries = /* @__PURE__ */ new Map();
|
|
1214
1203
|
try {
|
|
1215
|
-
if (
|
|
1204
|
+
if (existsSync4(this.plaintextFilePath)) unlinkSync(this.plaintextFilePath);
|
|
1216
1205
|
} catch {
|
|
1217
1206
|
}
|
|
1218
1207
|
try {
|
|
1219
|
-
if (
|
|
1208
|
+
if (existsSync4(this.encryptedFilePath)) unlinkSync(this.encryptedFilePath);
|
|
1220
1209
|
} catch {
|
|
1221
1210
|
}
|
|
1222
1211
|
}
|
|
@@ -1249,19 +1238,19 @@ var GateStore = class {
|
|
|
1249
1238
|
}
|
|
1250
1239
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
1251
1240
|
get plaintextFilePath() {
|
|
1252
|
-
return
|
|
1241
|
+
return join4(this.baseDir, "gates.json");
|
|
1253
1242
|
}
|
|
1254
1243
|
get encryptedFilePath() {
|
|
1255
|
-
return
|
|
1244
|
+
return join4(this.baseDir, "gates.enc.json");
|
|
1256
1245
|
}
|
|
1257
1246
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
1258
1247
|
loadPlaintext() {
|
|
1259
|
-
if (!
|
|
1260
|
-
|
|
1248
|
+
if (!existsSync4(this.plaintextFilePath)) {
|
|
1249
|
+
mkdirSync2(this.baseDir, { recursive: true });
|
|
1261
1250
|
return;
|
|
1262
1251
|
}
|
|
1263
1252
|
try {
|
|
1264
|
-
const raw =
|
|
1253
|
+
const raw = readFileSync5(this.plaintextFilePath, "utf-8");
|
|
1265
1254
|
const data = JSON.parse(raw);
|
|
1266
1255
|
this.entries = new Map(Object.entries(data.entries));
|
|
1267
1256
|
} catch {
|
|
@@ -1270,9 +1259,9 @@ var GateStore = class {
|
|
|
1270
1259
|
}
|
|
1271
1260
|
}
|
|
1272
1261
|
loadEncrypted() {
|
|
1273
|
-
if (!
|
|
1262
|
+
if (!existsSync4(this.encryptedFilePath)) return;
|
|
1274
1263
|
try {
|
|
1275
|
-
const raw =
|
|
1264
|
+
const raw = readFileSync5(this.encryptedFilePath, "utf-8");
|
|
1276
1265
|
const data = JSON.parse(raw);
|
|
1277
1266
|
this.entries = /* @__PURE__ */ new Map();
|
|
1278
1267
|
for (const [key, blob] of Object.entries(data.entries)) {
|
|
@@ -1296,8 +1285,8 @@ var GateStore = class {
|
|
|
1296
1285
|
version: 1,
|
|
1297
1286
|
entries: Object.fromEntries(this.entries)
|
|
1298
1287
|
};
|
|
1299
|
-
|
|
1300
|
-
|
|
1288
|
+
mkdirSync2(this.baseDir, { recursive: true, mode: 448 });
|
|
1289
|
+
writeFileSync2(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1301
1290
|
}
|
|
1302
1291
|
persistEncrypted() {
|
|
1303
1292
|
const data = {
|
|
@@ -1307,17 +1296,17 @@ var GateStore = class {
|
|
|
1307
1296
|
for (const [key, entry] of this.entries) {
|
|
1308
1297
|
data.entries[key] = this.encrypt(JSON.stringify(entry));
|
|
1309
1298
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1299
|
+
mkdirSync2(this.baseDir, { recursive: true, mode: 448 });
|
|
1300
|
+
writeFileSync2(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1312
1301
|
}
|
|
1313
1302
|
};
|
|
1314
1303
|
|
|
1315
1304
|
// src/lib/execution-log.ts
|
|
1316
|
-
import { mkdirSync as
|
|
1305
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
1317
1306
|
import { homedir as homedir2 } from "os";
|
|
1318
|
-
import { dirname as
|
|
1307
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
1319
1308
|
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
|
|
1320
|
-
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
1309
|
+
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ?? join5(homedir2(), ".suveren");
|
|
1321
1310
|
var MAX_AGE_SECONDS = 31 * 24 * 60 * 60;
|
|
1322
1311
|
var ExecutionLog = class {
|
|
1323
1312
|
entries = [];
|
|
@@ -1325,7 +1314,7 @@ var ExecutionLog = class {
|
|
|
1325
1314
|
vaultKey = null;
|
|
1326
1315
|
constructor(filePath) {
|
|
1327
1316
|
if (filePath && filePath.endsWith(".json")) {
|
|
1328
|
-
this.baseDir =
|
|
1317
|
+
this.baseDir = dirname3(filePath);
|
|
1329
1318
|
} else {
|
|
1330
1319
|
this.baseDir = filePath ?? DEFAULT_DIR2;
|
|
1331
1320
|
}
|
|
@@ -1336,13 +1325,13 @@ var ExecutionLog = class {
|
|
|
1336
1325
|
*/
|
|
1337
1326
|
setVaultKey(key) {
|
|
1338
1327
|
this.vaultKey = key;
|
|
1339
|
-
if (
|
|
1328
|
+
if (existsSync5(this.encryptedFilePath)) {
|
|
1340
1329
|
this.loadEncrypted();
|
|
1341
1330
|
} else if (this.entries.length > 0) {
|
|
1342
1331
|
this.persistEncrypted();
|
|
1343
|
-
if (
|
|
1332
|
+
if (existsSync5(this.plaintextFilePath)) {
|
|
1344
1333
|
try {
|
|
1345
|
-
|
|
1334
|
+
unlinkSync2(this.plaintextFilePath);
|
|
1346
1335
|
} catch {
|
|
1347
1336
|
}
|
|
1348
1337
|
}
|
|
@@ -1444,19 +1433,19 @@ var ExecutionLog = class {
|
|
|
1444
1433
|
}
|
|
1445
1434
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
1446
1435
|
get plaintextFilePath() {
|
|
1447
|
-
return
|
|
1436
|
+
return join5(this.baseDir, "execution-log.json");
|
|
1448
1437
|
}
|
|
1449
1438
|
get encryptedFilePath() {
|
|
1450
|
-
return
|
|
1439
|
+
return join5(this.baseDir, "execution-log.enc.json");
|
|
1451
1440
|
}
|
|
1452
1441
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
1453
1442
|
loadPlaintext() {
|
|
1454
|
-
if (!
|
|
1455
|
-
|
|
1443
|
+
if (!existsSync5(this.plaintextFilePath)) {
|
|
1444
|
+
mkdirSync3(this.baseDir, { recursive: true });
|
|
1456
1445
|
return;
|
|
1457
1446
|
}
|
|
1458
1447
|
try {
|
|
1459
|
-
const raw =
|
|
1448
|
+
const raw = readFileSync6(this.plaintextFilePath, "utf-8");
|
|
1460
1449
|
const data = JSON.parse(raw);
|
|
1461
1450
|
this.entries = data.entries;
|
|
1462
1451
|
this.prune();
|
|
@@ -1466,9 +1455,9 @@ var ExecutionLog = class {
|
|
|
1466
1455
|
}
|
|
1467
1456
|
}
|
|
1468
1457
|
loadEncrypted() {
|
|
1469
|
-
if (!
|
|
1458
|
+
if (!existsSync5(this.encryptedFilePath)) return;
|
|
1470
1459
|
try {
|
|
1471
|
-
const raw =
|
|
1460
|
+
const raw = readFileSync6(this.encryptedFilePath, "utf-8");
|
|
1472
1461
|
const data = JSON.parse(raw);
|
|
1473
1462
|
const decrypted = this.decrypt(data.blob);
|
|
1474
1463
|
const logFile = JSON.parse(decrypted);
|
|
@@ -1488,8 +1477,8 @@ var ExecutionLog = class {
|
|
|
1488
1477
|
}
|
|
1489
1478
|
persistPlaintext() {
|
|
1490
1479
|
const data = { version: 1, entries: this.entries };
|
|
1491
|
-
|
|
1492
|
-
|
|
1480
|
+
mkdirSync3(this.baseDir, { recursive: true, mode: 448 });
|
|
1481
|
+
writeFileSync3(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1493
1482
|
}
|
|
1494
1483
|
persistEncrypted() {
|
|
1495
1484
|
const logFile = { version: 1, entries: this.entries };
|
|
@@ -1497,8 +1486,8 @@ var ExecutionLog = class {
|
|
|
1497
1486
|
version: 1,
|
|
1498
1487
|
blob: this.encrypt(JSON.stringify(logFile))
|
|
1499
1488
|
};
|
|
1500
|
-
|
|
1501
|
-
|
|
1489
|
+
mkdirSync3(this.baseDir, { recursive: true, mode: 448 });
|
|
1490
|
+
writeFileSync3(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1502
1491
|
}
|
|
1503
1492
|
};
|
|
1504
1493
|
function windowCutoff(window, now) {
|
|
@@ -1515,11 +1504,11 @@ function windowCutoff(window, now) {
|
|
|
1515
1504
|
}
|
|
1516
1505
|
|
|
1517
1506
|
// src/lib/denial-log.ts
|
|
1518
|
-
import { mkdirSync as
|
|
1507
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync6, unlinkSync as unlinkSync3 } from "fs";
|
|
1519
1508
|
import { homedir as homedir3 } from "os";
|
|
1520
|
-
import { dirname as
|
|
1509
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
1521
1510
|
import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, randomBytes as randomBytes3 } from "crypto";
|
|
1522
|
-
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ??
|
|
1511
|
+
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ?? join6(homedir3(), ".suveren");
|
|
1523
1512
|
var MAX_RECORDS = 200;
|
|
1524
1513
|
var MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1525
1514
|
var DenialLog = class {
|
|
@@ -1527,18 +1516,18 @@ var DenialLog = class {
|
|
|
1527
1516
|
baseDir;
|
|
1528
1517
|
vaultKey = null;
|
|
1529
1518
|
constructor(filePath) {
|
|
1530
|
-
this.baseDir = filePath && filePath.endsWith(".json") ?
|
|
1519
|
+
this.baseDir = filePath && filePath.endsWith(".json") ? dirname4(filePath) : filePath ?? DEFAULT_DIR3;
|
|
1531
1520
|
this.loadPlaintext();
|
|
1532
1521
|
}
|
|
1533
1522
|
setVaultKey(key) {
|
|
1534
1523
|
this.vaultKey = key;
|
|
1535
|
-
if (
|
|
1524
|
+
if (existsSync6(this.encryptedFilePath)) {
|
|
1536
1525
|
this.loadEncrypted();
|
|
1537
1526
|
} else if (this.records.length > 0) {
|
|
1538
1527
|
this.persistEncrypted();
|
|
1539
|
-
if (
|
|
1528
|
+
if (existsSync6(this.plaintextFilePath)) {
|
|
1540
1529
|
try {
|
|
1541
|
-
|
|
1530
|
+
unlinkSync3(this.plaintextFilePath);
|
|
1542
1531
|
} catch {
|
|
1543
1532
|
}
|
|
1544
1533
|
}
|
|
@@ -1586,18 +1575,18 @@ var DenialLog = class {
|
|
|
1586
1575
|
return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]).toString("utf8");
|
|
1587
1576
|
}
|
|
1588
1577
|
get plaintextFilePath() {
|
|
1589
|
-
return
|
|
1578
|
+
return join6(this.baseDir, "denials.json");
|
|
1590
1579
|
}
|
|
1591
1580
|
get encryptedFilePath() {
|
|
1592
|
-
return
|
|
1581
|
+
return join6(this.baseDir, "denials.enc.json");
|
|
1593
1582
|
}
|
|
1594
1583
|
loadPlaintext() {
|
|
1595
|
-
if (!
|
|
1596
|
-
|
|
1584
|
+
if (!existsSync6(this.plaintextFilePath)) {
|
|
1585
|
+
mkdirSync4(this.baseDir, { recursive: true });
|
|
1597
1586
|
return;
|
|
1598
1587
|
}
|
|
1599
1588
|
try {
|
|
1600
|
-
const data = JSON.parse(
|
|
1589
|
+
const data = JSON.parse(readFileSync7(this.plaintextFilePath, "utf-8"));
|
|
1601
1590
|
this.records = data.records ?? [];
|
|
1602
1591
|
} catch {
|
|
1603
1592
|
console.error(`[DenialLog] Could not parse ${this.plaintextFilePath}, starting fresh`);
|
|
@@ -1605,9 +1594,9 @@ var DenialLog = class {
|
|
|
1605
1594
|
}
|
|
1606
1595
|
}
|
|
1607
1596
|
loadEncrypted() {
|
|
1608
|
-
if (!
|
|
1597
|
+
if (!existsSync6(this.encryptedFilePath)) return;
|
|
1609
1598
|
try {
|
|
1610
|
-
const data = JSON.parse(
|
|
1599
|
+
const data = JSON.parse(readFileSync7(this.encryptedFilePath, "utf-8"));
|
|
1611
1600
|
const logFile = JSON.parse(this.decrypt(data.blob));
|
|
1612
1601
|
this.records = logFile.records ?? [];
|
|
1613
1602
|
} catch (err) {
|
|
@@ -1621,23 +1610,23 @@ var DenialLog = class {
|
|
|
1621
1610
|
}
|
|
1622
1611
|
persistPlaintext() {
|
|
1623
1612
|
const data = { version: 1, records: this.records };
|
|
1624
|
-
|
|
1625
|
-
|
|
1613
|
+
mkdirSync4(this.baseDir, { recursive: true, mode: 448 });
|
|
1614
|
+
writeFileSync4(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1626
1615
|
}
|
|
1627
1616
|
persistEncrypted() {
|
|
1628
1617
|
const logFile = { version: 1, records: this.records };
|
|
1629
1618
|
const data = { version: 1, blob: this.encrypt(JSON.stringify(logFile)) };
|
|
1630
|
-
|
|
1631
|
-
|
|
1619
|
+
mkdirSync4(this.baseDir, { recursive: true, mode: 448 });
|
|
1620
|
+
writeFileSync4(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1632
1621
|
}
|
|
1633
1622
|
};
|
|
1634
1623
|
|
|
1635
1624
|
// src/lib/receipt-archive.ts
|
|
1636
|
-
import { mkdirSync as
|
|
1625
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync7, unlinkSync as unlinkSync4 } from "fs";
|
|
1637
1626
|
import { homedir as homedir4 } from "os";
|
|
1638
|
-
import { dirname as
|
|
1627
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
1639
1628
|
import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes4 } from "crypto";
|
|
1640
|
-
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ??
|
|
1629
|
+
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ?? join7(homedir4(), ".suveren");
|
|
1641
1630
|
function stripPreviews(proposal) {
|
|
1642
1631
|
const args = proposal.toolArgs;
|
|
1643
1632
|
if (!args || typeof args !== "object") return proposal;
|
|
@@ -1651,7 +1640,7 @@ var ReceiptArchive = class {
|
|
|
1651
1640
|
vaultKey = null;
|
|
1652
1641
|
constructor(filePath) {
|
|
1653
1642
|
if (filePath && filePath.endsWith(".json")) {
|
|
1654
|
-
this.baseDir =
|
|
1643
|
+
this.baseDir = dirname5(filePath);
|
|
1655
1644
|
} else {
|
|
1656
1645
|
this.baseDir = filePath ?? DEFAULT_DIR4;
|
|
1657
1646
|
}
|
|
@@ -1662,13 +1651,13 @@ var ReceiptArchive = class {
|
|
|
1662
1651
|
*/
|
|
1663
1652
|
setVaultKey(key) {
|
|
1664
1653
|
this.vaultKey = key;
|
|
1665
|
-
if (
|
|
1654
|
+
if (existsSync7(this.encryptedFilePath)) {
|
|
1666
1655
|
this.loadEncrypted();
|
|
1667
1656
|
} else if (this.data.receipts.length > 0 || Object.keys(this.data.authorizations).length > 0) {
|
|
1668
1657
|
this.persistEncrypted();
|
|
1669
|
-
if (
|
|
1658
|
+
if (existsSync7(this.plaintextFilePath)) {
|
|
1670
1659
|
try {
|
|
1671
|
-
|
|
1660
|
+
unlinkSync4(this.plaintextFilePath);
|
|
1672
1661
|
} catch {
|
|
1673
1662
|
}
|
|
1674
1663
|
}
|
|
@@ -1680,7 +1669,7 @@ var ReceiptArchive = class {
|
|
|
1680
1669
|
* this state — that would report a hollow success over unreadable evidence.
|
|
1681
1670
|
*/
|
|
1682
1671
|
isLocked() {
|
|
1683
|
-
return this.vaultKey === null &&
|
|
1672
|
+
return this.vaultKey === null && existsSync7(this.encryptedFilePath);
|
|
1684
1673
|
}
|
|
1685
1674
|
/**
|
|
1686
1675
|
* Archive a signed receipt (and the grant it was issued under).
|
|
@@ -1688,8 +1677,8 @@ var ReceiptArchive = class {
|
|
|
1688
1677
|
*/
|
|
1689
1678
|
record(entry, now = Math.floor(Date.now() / 1e3)) {
|
|
1690
1679
|
let changed = false;
|
|
1691
|
-
const
|
|
1692
|
-
const alreadyArchived =
|
|
1680
|
+
const receiptId2 = typeof entry.receipt?.id === "string" ? entry.receipt.id : void 0;
|
|
1681
|
+
const alreadyArchived = receiptId2 !== void 0 && this.data.receipts.some((r) => r.receipt.id === receiptId2);
|
|
1693
1682
|
if (!alreadyArchived) {
|
|
1694
1683
|
this.data.receipts.push({
|
|
1695
1684
|
archivedAt: now,
|
|
@@ -1781,19 +1770,19 @@ var ReceiptArchive = class {
|
|
|
1781
1770
|
}
|
|
1782
1771
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
1783
1772
|
get plaintextFilePath() {
|
|
1784
|
-
return
|
|
1773
|
+
return join7(this.baseDir, "receipt-archive.json");
|
|
1785
1774
|
}
|
|
1786
1775
|
get encryptedFilePath() {
|
|
1787
|
-
return
|
|
1776
|
+
return join7(this.baseDir, "receipt-archive.enc.json");
|
|
1788
1777
|
}
|
|
1789
1778
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
1790
1779
|
loadPlaintext() {
|
|
1791
|
-
if (!
|
|
1792
|
-
|
|
1780
|
+
if (!existsSync7(this.plaintextFilePath)) {
|
|
1781
|
+
mkdirSync5(this.baseDir, { recursive: true });
|
|
1793
1782
|
return;
|
|
1794
1783
|
}
|
|
1795
1784
|
try {
|
|
1796
|
-
const raw =
|
|
1785
|
+
const raw = readFileSync8(this.plaintextFilePath, "utf-8");
|
|
1797
1786
|
const parsed = JSON.parse(raw);
|
|
1798
1787
|
this.data = {
|
|
1799
1788
|
version: 1,
|
|
@@ -1803,18 +1792,18 @@ var ReceiptArchive = class {
|
|
|
1803
1792
|
} catch {
|
|
1804
1793
|
console.error(`[ReceiptArchive] Could not parse ${this.plaintextFilePath} \u2014 preserving as .corrupt`);
|
|
1805
1794
|
try {
|
|
1806
|
-
|
|
1795
|
+
writeFileSync5(
|
|
1807
1796
|
`${this.plaintextFilePath}.corrupt-${Date.now()}`,
|
|
1808
|
-
|
|
1797
|
+
readFileSync8(this.plaintextFilePath)
|
|
1809
1798
|
);
|
|
1810
1799
|
} catch {
|
|
1811
1800
|
}
|
|
1812
1801
|
}
|
|
1813
1802
|
}
|
|
1814
1803
|
loadEncrypted() {
|
|
1815
|
-
if (!
|
|
1804
|
+
if (!existsSync7(this.encryptedFilePath)) return;
|
|
1816
1805
|
try {
|
|
1817
|
-
const raw =
|
|
1806
|
+
const raw = readFileSync8(this.encryptedFilePath, "utf-8");
|
|
1818
1807
|
const data = JSON.parse(raw);
|
|
1819
1808
|
const decrypted = this.decrypt(data.blob);
|
|
1820
1809
|
const parsed = JSON.parse(decrypted);
|
|
@@ -1826,9 +1815,9 @@ var ReceiptArchive = class {
|
|
|
1826
1815
|
} catch (err) {
|
|
1827
1816
|
console.error(`[ReceiptArchive] Could not decrypt ${this.encryptedFilePath} \u2014 preserving as .corrupt:`, err);
|
|
1828
1817
|
try {
|
|
1829
|
-
|
|
1818
|
+
writeFileSync5(
|
|
1830
1819
|
`${this.encryptedFilePath}.corrupt-${Date.now()}`,
|
|
1831
|
-
|
|
1820
|
+
readFileSync8(this.encryptedFilePath)
|
|
1832
1821
|
);
|
|
1833
1822
|
} catch {
|
|
1834
1823
|
}
|
|
@@ -1842,29 +1831,29 @@ var ReceiptArchive = class {
|
|
|
1842
1831
|
}
|
|
1843
1832
|
}
|
|
1844
1833
|
persistPlaintext() {
|
|
1845
|
-
|
|
1846
|
-
|
|
1834
|
+
mkdirSync5(this.baseDir, { recursive: true, mode: 448 });
|
|
1835
|
+
writeFileSync5(this.plaintextFilePath, JSON.stringify(this.data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1847
1836
|
}
|
|
1848
1837
|
persistEncrypted() {
|
|
1849
1838
|
const data = {
|
|
1850
1839
|
version: 1,
|
|
1851
1840
|
blob: this.encrypt(JSON.stringify(this.data))
|
|
1852
1841
|
};
|
|
1853
|
-
|
|
1854
|
-
|
|
1842
|
+
mkdirSync5(this.baseDir, { recursive: true, mode: 448 });
|
|
1843
|
+
writeFileSync5(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1855
1844
|
}
|
|
1856
1845
|
};
|
|
1857
1846
|
|
|
1858
1847
|
// src/lib/execution-journal.ts
|
|
1859
|
-
import { mkdirSync as
|
|
1848
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync8, renameSync } from "fs";
|
|
1860
1849
|
import { homedir as homedir5 } from "os";
|
|
1861
|
-
import { dirname as
|
|
1862
|
-
import { createHash as
|
|
1863
|
-
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ??
|
|
1850
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
1851
|
+
import { createHash as createHash2 } from "crypto";
|
|
1852
|
+
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ?? join8(homedir5(), ".suveren");
|
|
1864
1853
|
var FILE_NAME = "execution-journal.json";
|
|
1865
1854
|
var MAX_AGE_SECONDS2 = 90 * 24 * 60 * 60;
|
|
1866
1855
|
function hashToolArgs(args) {
|
|
1867
|
-
return "sha256:" +
|
|
1856
|
+
return "sha256:" + createHash2("sha256").update(stableStringify(args)).digest("hex");
|
|
1868
1857
|
}
|
|
1869
1858
|
function stableStringify(value) {
|
|
1870
1859
|
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
|
|
@@ -1876,8 +1865,8 @@ function stableStringify(value) {
|
|
|
1876
1865
|
var ExecutionJournal = class {
|
|
1877
1866
|
filePath;
|
|
1878
1867
|
constructor(filePath) {
|
|
1879
|
-
const baseDir = filePath && filePath.endsWith(".json") ?
|
|
1880
|
-
this.filePath =
|
|
1868
|
+
const baseDir = filePath && filePath.endsWith(".json") ? dirname6(filePath) : filePath ?? DEFAULT_DIR5;
|
|
1869
|
+
this.filePath = join8(baseDir, FILE_NAME);
|
|
1881
1870
|
}
|
|
1882
1871
|
/** Look a ticket up. Always reads from disk — another process may have written. */
|
|
1883
1872
|
get(ticketId) {
|
|
@@ -1916,9 +1905,9 @@ var ExecutionJournal = class {
|
|
|
1916
1905
|
}
|
|
1917
1906
|
// ─── Persistence ────────────────────────────────────────────────────────
|
|
1918
1907
|
load() {
|
|
1919
|
-
if (!
|
|
1908
|
+
if (!existsSync8(this.filePath)) return { version: 1, entries: [] };
|
|
1920
1909
|
try {
|
|
1921
|
-
const parsed = JSON.parse(
|
|
1910
|
+
const parsed = JSON.parse(readFileSync9(this.filePath, "utf8"));
|
|
1922
1911
|
if (parsed?.version !== 1 || !Array.isArray(parsed.entries)) {
|
|
1923
1912
|
throw new Error("unexpected shape");
|
|
1924
1913
|
}
|
|
@@ -1932,9 +1921,9 @@ var ExecutionJournal = class {
|
|
|
1932
1921
|
persist(file) {
|
|
1933
1922
|
const cutoff = nowSec() - MAX_AGE_SECONDS2;
|
|
1934
1923
|
file.entries = file.entries.filter((e) => e.startedAt >= cutoff);
|
|
1935
|
-
|
|
1924
|
+
mkdirSync6(dirname6(this.filePath), { recursive: true, mode: 448 });
|
|
1936
1925
|
const tmp = this.filePath + ".tmp";
|
|
1937
|
-
|
|
1926
|
+
writeFileSync6(tmp, JSON.stringify(file, null, 2), { mode: 384 });
|
|
1938
1927
|
renameSync(tmp, this.filePath);
|
|
1939
1928
|
}
|
|
1940
1929
|
};
|
|
@@ -1943,10 +1932,10 @@ function nowSec() {
|
|
|
1943
1932
|
}
|
|
1944
1933
|
|
|
1945
1934
|
// src/lib/proposal-submission-store.ts
|
|
1946
|
-
import { mkdirSync as
|
|
1935
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync9, renameSync as renameSync2 } from "fs";
|
|
1947
1936
|
import { homedir as homedir6 } from "os";
|
|
1948
|
-
import { dirname as
|
|
1949
|
-
var DEFAULT_DIR6 = process.env.SUVEREN_DATA_DIR ??
|
|
1937
|
+
import { dirname as dirname7, join as join9 } from "path";
|
|
1938
|
+
var DEFAULT_DIR6 = process.env.SUVEREN_DATA_DIR ?? join9(homedir6(), ".suveren");
|
|
1950
1939
|
var FILE_NAME2 = "proposal-submissions.json";
|
|
1951
1940
|
var MAX_AGE_SECONDS3 = 90 * 24 * 60 * 60;
|
|
1952
1941
|
function nowSec2() {
|
|
@@ -1955,8 +1944,8 @@ function nowSec2() {
|
|
|
1955
1944
|
var ProposalSubmissionStore = class {
|
|
1956
1945
|
filePath;
|
|
1957
1946
|
constructor(filePath) {
|
|
1958
|
-
const baseDir = filePath && filePath.endsWith(".json") ?
|
|
1959
|
-
this.filePath =
|
|
1947
|
+
const baseDir = filePath && filePath.endsWith(".json") ? dirname7(filePath) : filePath ?? DEFAULT_DIR6;
|
|
1948
|
+
this.filePath = join9(baseDir, FILE_NAME2);
|
|
1960
1949
|
}
|
|
1961
1950
|
/** Record what was submitted, right after a successful submitProposal(). */
|
|
1962
1951
|
record(entry) {
|
|
@@ -1979,9 +1968,9 @@ var ProposalSubmissionStore = class {
|
|
|
1979
1968
|
return this.load().entries.find((e) => e.proposalId === proposalId);
|
|
1980
1969
|
}
|
|
1981
1970
|
load() {
|
|
1982
|
-
if (!
|
|
1971
|
+
if (!existsSync9(this.filePath)) return { version: 1, entries: [] };
|
|
1983
1972
|
try {
|
|
1984
|
-
const parsed = JSON.parse(
|
|
1973
|
+
const parsed = JSON.parse(readFileSync10(this.filePath, "utf-8"));
|
|
1985
1974
|
const cutoff = nowSec2() - MAX_AGE_SECONDS3;
|
|
1986
1975
|
parsed.entries = (parsed.entries ?? []).filter((e) => e.submittedAt >= cutoff);
|
|
1987
1976
|
return parsed;
|
|
@@ -1990,9 +1979,9 @@ var ProposalSubmissionStore = class {
|
|
|
1990
1979
|
}
|
|
1991
1980
|
}
|
|
1992
1981
|
persist(file) {
|
|
1993
|
-
|
|
1982
|
+
mkdirSync7(dirname7(this.filePath), { recursive: true, mode: 448 });
|
|
1994
1983
|
const tmpPath = `${this.filePath}.tmp`;
|
|
1995
|
-
|
|
1984
|
+
writeFileSync7(tmpPath, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1996
1985
|
renameSync2(tmpPath, this.filePath);
|
|
1997
1986
|
}
|
|
1998
1987
|
};
|
|
@@ -2088,6 +2077,647 @@ var MCPGatekeeper = class {
|
|
|
2088
2077
|
}
|
|
2089
2078
|
};
|
|
2090
2079
|
|
|
2080
|
+
// src/lib/report/report-store.ts
|
|
2081
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync10, unlinkSync as unlinkSync5 } from "fs";
|
|
2082
|
+
import { homedir as homedir7 } from "os";
|
|
2083
|
+
import { dirname as dirname8, join as join10 } from "path";
|
|
2084
|
+
import { createCipheriv as createCipheriv5, createDecipheriv as createDecipheriv5, randomBytes as randomBytes5 } from "crypto";
|
|
2085
|
+
|
|
2086
|
+
// src/lib/report-brief.ts
|
|
2087
|
+
var REPORT_ELEMENTS = [
|
|
2088
|
+
"sv-ticket",
|
|
2089
|
+
"sv-approval",
|
|
2090
|
+
"sv-mandate",
|
|
2091
|
+
"sv-record",
|
|
2092
|
+
"sv-case",
|
|
2093
|
+
"sv-metric"
|
|
2094
|
+
];
|
|
2095
|
+
var REPORT_BRIEF = `How to write the report
|
|
2096
|
+
|
|
2097
|
+
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.
|
|
2098
|
+
|
|
2099
|
+
Who reads it: managers who are not technical. Plain words, short sentences, the result first.
|
|
2100
|
+
|
|
2101
|
+
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.
|
|
2102
|
+
|
|
2103
|
+
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.
|
|
2104
|
+
|
|
2105
|
+
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:
|
|
2106
|
+
|
|
2107
|
+
- <sv-ticket ref="TICKET_ID"></sv-ticket> \u2014 one action you took, with its signed ticket.
|
|
2108
|
+
- <sv-approval ticket="TICKET_ID"></sv-approval> \u2014 who approved that action, when, and how long it waited.
|
|
2109
|
+
- <sv-mandate ticket="TICKET_ID"></sv-mandate> \u2014 the authority the action ran under: limits, mode, intent.
|
|
2110
|
+
- <sv-record system="email|crm|erp" ref="RECORD_ID"></sv-record> \u2014 an email, CRM entry, quote or order.
|
|
2111
|
+
- <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.
|
|
2112
|
+
- <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".
|
|
2113
|
+
|
|
2114
|
+
Rules:
|
|
2115
|
+
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.
|
|
2116
|
+
2. Use sv-metric for every headline number. Numbers you compute yourself are shown as "AI analysis \u2014 not verified".
|
|
2117
|
+
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.
|
|
2118
|
+
4. A wrong or unknown reference is shown as "not verifiable". Do not guess IDs \u2014 read them first.
|
|
2119
|
+
5. Never claim more than the elements prove. Say plainly what went wrong or waited for a person.
|
|
2120
|
+
|
|
2121
|
+
Suggested structure:
|
|
2122
|
+
1. The result in one sentence, then 3\u20134 sv-metric figures (completed, median time, share without approval, approvals).
|
|
2123
|
+
2. All cases at a glance \u2014 a table with one row per case, linked to its sv-case below.
|
|
2124
|
+
3. Each case: sv-case, then the sv-ticket and sv-approval elements that explain it.
|
|
2125
|
+
4. What was stopped or needed a person, and why (sv-approval, refusals).
|
|
2126
|
+
5. Your assessment: what worked, what slowed things down, what a different mandate would change \u2014 marked as your view.
|
|
2127
|
+
|
|
2128
|
+
Update the report whenever cases progress; each write replaces the previous report.`;
|
|
2129
|
+
|
|
2130
|
+
// src/lib/report/sanitize.ts
|
|
2131
|
+
import sanitizeHtmlLib from "sanitize-html";
|
|
2132
|
+
var FORBIDDEN_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "link"]);
|
|
2133
|
+
function isUrlAttribute(name) {
|
|
2134
|
+
const n = name.toLowerCase();
|
|
2135
|
+
return n === "href" || n === "src" || n === "xlink:href" || n.endsWith(":href");
|
|
2136
|
+
}
|
|
2137
|
+
function isAllowedUrl(value) {
|
|
2138
|
+
const v = value.trim();
|
|
2139
|
+
if (v.startsWith("#")) return true;
|
|
2140
|
+
if (/^data:image\//i.test(v)) return true;
|
|
2141
|
+
return false;
|
|
2142
|
+
}
|
|
2143
|
+
function sanitizeReportHtml(html) {
|
|
2144
|
+
return sanitizeHtmlLib(html, {
|
|
2145
|
+
// Allow every tag/attribute by default — see module doc comment for why
|
|
2146
|
+
// this is deny-by-name rather than allow-by-name. `transformTags['*']`
|
|
2147
|
+
// below is then the ONLY attribute filter (allowedAttributes: false
|
|
2148
|
+
// skips sanitize-html's own, so there is exactly one place attributes
|
|
2149
|
+
// are decided, not two that could disagree). Logs a harmless warning for
|
|
2150
|
+
// "script"/"style" on every call (sanitize-html's own vulnerable-tag
|
|
2151
|
+
// notice, fired from its internal tag list even though we pass `false`,
|
|
2152
|
+
// not an array containing them) — `allowVulnerableTags` is the
|
|
2153
|
+
// documented way to accept that, and <script> is still removed, by
|
|
2154
|
+
// `exclusiveFilter` below, regardless of this flag.
|
|
2155
|
+
allowedTags: false,
|
|
2156
|
+
allowedAttributes: false,
|
|
2157
|
+
allowVulnerableTags: true,
|
|
2158
|
+
// sanitize-html's OWN scheme check (independent of transformTags, and
|
|
2159
|
+
// applied to it/src/cite only — NOT xlink:href, which is why the
|
|
2160
|
+
// transformTags filter above is the one actually relied on for SVG) uses
|
|
2161
|
+
// this list; it has no "data" scheme by default, which would undo our
|
|
2162
|
+
// own decision to keep `data:image/*` sources. Nothing wider is opened:
|
|
2163
|
+
// transformTags has already removed every href/src that isn't a
|
|
2164
|
+
// fragment link or a data:image/* URI by the time this check runs.
|
|
2165
|
+
allowedSchemes: [...sanitizeHtmlLib.defaults.allowedSchemes, "data"],
|
|
2166
|
+
// Preserve attribute name casing — inline SVG uses camelCase attributes
|
|
2167
|
+
// (viewBox, preserveAspectRatio, …) that lowercasing would silently
|
|
2168
|
+
// break. Safe for the `on*`/url checks below: both compare case-folded.
|
|
2169
|
+
parser: { lowerCaseAttributeNames: false },
|
|
2170
|
+
// Removes the named tag AND all of its children/text — the only correct
|
|
2171
|
+
// behaviour for <script>: dropping just the tag would leave its JS as
|
|
2172
|
+
// visible text, but the content must not survive either.
|
|
2173
|
+
exclusiveFilter: (frame) => {
|
|
2174
|
+
const tag = frame.tag.toLowerCase();
|
|
2175
|
+
if (FORBIDDEN_TAGS.has(tag)) return true;
|
|
2176
|
+
if (tag === "meta" && Object.keys(frame.attribs).some((a) => a.toLowerCase() === "http-equiv")) {
|
|
2177
|
+
return true;
|
|
2178
|
+
}
|
|
2179
|
+
return false;
|
|
2180
|
+
},
|
|
2181
|
+
transformTags: {
|
|
2182
|
+
"*": (tagName, attribs) => {
|
|
2183
|
+
const kept = {};
|
|
2184
|
+
for (const [name, value] of Object.entries(attribs)) {
|
|
2185
|
+
if (name.toLowerCase().startsWith("on")) continue;
|
|
2186
|
+
if (isUrlAttribute(name) && !isAllowedUrl(value)) continue;
|
|
2187
|
+
kept[name] = value;
|
|
2188
|
+
}
|
|
2189
|
+
return { tagName, attribs: kept };
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
// src/lib/report/record-resolvers.ts
|
|
2196
|
+
function findById(rows, id) {
|
|
2197
|
+
return rows.find((r) => r.id === id);
|
|
2198
|
+
}
|
|
2199
|
+
function resolveEmailRecord(exportData, ref) {
|
|
2200
|
+
const inInbox = findById(exportData.inbox, ref);
|
|
2201
|
+
if (inInbox) return { status: "verified", data: { kind: "message", folder: "inbox", ...inInbox } };
|
|
2202
|
+
const inSent = findById(exportData.sent, ref);
|
|
2203
|
+
if (inSent) return { status: "verified", data: { kind: "message", folder: "sent", ...inSent } };
|
|
2204
|
+
const change = findById(exportData.changes, ref);
|
|
2205
|
+
if (change) return { status: "verified", data: { kind: "change", causingReceiptId: change.receipt_id ?? null, ...change } };
|
|
2206
|
+
const refusal = findById(exportData.refusals, ref);
|
|
2207
|
+
if (refusal) return { status: "verified", data: { kind: "refusal", ...refusal } };
|
|
2208
|
+
return { status: "unverifiable", reason: `No email record "${ref}" in the simulator export.` };
|
|
2209
|
+
}
|
|
2210
|
+
function resolveErpRecord(exportData, ref) {
|
|
2211
|
+
const quote = findById(exportData.quotes, ref) ?? exportData.quotes.find((q) => q.number === ref);
|
|
2212
|
+
if (quote) return { status: "verified", data: { kind: "quote", ...quote } };
|
|
2213
|
+
const order = findById(exportData.orders, ref) ?? exportData.orders.find((o) => o.number === ref);
|
|
2214
|
+
if (order) return { status: "verified", data: { kind: "order", ...order } };
|
|
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 ERP record "${ref}" in the simulator export.` };
|
|
2220
|
+
}
|
|
2221
|
+
function resolveCrmRecord(exportData, ref) {
|
|
2222
|
+
for (const [kind, rows] of [
|
|
2223
|
+
["contact", exportData.contacts],
|
|
2224
|
+
["deal", exportData.deals],
|
|
2225
|
+
["task", exportData.tasks],
|
|
2226
|
+
["activity", exportData.activities]
|
|
2227
|
+
]) {
|
|
2228
|
+
const row = findById(rows, ref);
|
|
2229
|
+
if (row) return { status: "verified", data: { kind, ...row } };
|
|
2230
|
+
}
|
|
2231
|
+
const change = findById(exportData.changes, ref);
|
|
2232
|
+
if (change) return { status: "verified", data: { kind: "change", causingReceiptId: change.receipt_id ?? null, ...change } };
|
|
2233
|
+
const refusal = findById(exportData.refusals, ref);
|
|
2234
|
+
if (refusal) return { status: "verified", data: { kind: "refusal", ...refusal } };
|
|
2235
|
+
return { status: "unverifiable", reason: `No CRM record "${ref}" in the simulator export.` };
|
|
2236
|
+
}
|
|
2237
|
+
function resolveRecord(system, exportData, ref) {
|
|
2238
|
+
if (system === "email") return resolveEmailRecord(exportData, ref);
|
|
2239
|
+
if (system === "erp") return resolveErpRecord(exportData, ref);
|
|
2240
|
+
return resolveCrmRecord(exportData, ref);
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
// src/lib/report/metric-resolvers.ts
|
|
2244
|
+
var METRIC_KINDS = [
|
|
2245
|
+
"completed",
|
|
2246
|
+
"median-time",
|
|
2247
|
+
"average-time",
|
|
2248
|
+
"without-approval",
|
|
2249
|
+
"approvals",
|
|
2250
|
+
"median-approval-wait",
|
|
2251
|
+
"tickets",
|
|
2252
|
+
"refusals"
|
|
2253
|
+
];
|
|
2254
|
+
var UnknownMetricKindError = class extends Error {
|
|
2255
|
+
constructor(kind) {
|
|
2256
|
+
super(`Unknown sv-metric kind "${kind}" \u2014 expected one of: ${METRIC_KINDS.join(", ")}.`);
|
|
2257
|
+
this.name = "UnknownMetricKindError";
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
function median(values) {
|
|
2261
|
+
if (values.length === 0) return 0;
|
|
2262
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
2263
|
+
const mid = Math.floor(sorted.length / 2);
|
|
2264
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
2265
|
+
}
|
|
2266
|
+
function average(values) {
|
|
2267
|
+
if (values.length === 0) return 0;
|
|
2268
|
+
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
2269
|
+
}
|
|
2270
|
+
function computeMetric(kind, cases, refusalTimes = []) {
|
|
2271
|
+
switch (kind) {
|
|
2272
|
+
case "completed":
|
|
2273
|
+
return cases.length;
|
|
2274
|
+
case "median-time":
|
|
2275
|
+
return median(cases.map((c) => c.totalDurationSeconds));
|
|
2276
|
+
case "average-time":
|
|
2277
|
+
return average(cases.map((c) => c.totalDurationSeconds));
|
|
2278
|
+
case "without-approval": {
|
|
2279
|
+
if (cases.length === 0) return 0;
|
|
2280
|
+
const withoutApproval = cases.filter((c) => c.approvals.length === 0).length;
|
|
2281
|
+
return withoutApproval / cases.length;
|
|
2282
|
+
}
|
|
2283
|
+
case "approvals":
|
|
2284
|
+
return cases.reduce((sum, c) => sum + c.approvals.length, 0);
|
|
2285
|
+
case "median-approval-wait":
|
|
2286
|
+
return median(cases.flatMap((c) => c.approvals.map((a) => a.waitSeconds).filter((w) => typeof w === "number")));
|
|
2287
|
+
case "tickets": {
|
|
2288
|
+
const ids = new Set(cases.flatMap((c) => c.ticketIds));
|
|
2289
|
+
return ids.size;
|
|
2290
|
+
}
|
|
2291
|
+
case "refusals":
|
|
2292
|
+
return cases.reduce(
|
|
2293
|
+
(sum, c) => sum + refusalTimes.filter((t) => t >= c.startTime && t <= c.goalTime).length,
|
|
2294
|
+
0
|
|
2295
|
+
);
|
|
2296
|
+
default:
|
|
2297
|
+
throw new UnknownMetricKindError(kind);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
// src/lib/report/types.ts
|
|
2302
|
+
function isEmailExport(v) {
|
|
2303
|
+
const o = v;
|
|
2304
|
+
return !!o && Array.isArray(o.inbox) && Array.isArray(o.sent) && Array.isArray(o.changes) && Array.isArray(o.refusals);
|
|
2305
|
+
}
|
|
2306
|
+
function isErpExport(v) {
|
|
2307
|
+
const o = v;
|
|
2308
|
+
return !!o && Array.isArray(o.quotes) && Array.isArray(o.orders) && Array.isArray(o.changes) && Array.isArray(o.refusals);
|
|
2309
|
+
}
|
|
2310
|
+
function isCrmExport(v) {
|
|
2311
|
+
const o = v;
|
|
2312
|
+
return !!o && Array.isArray(o.contacts) && Array.isArray(o.deals) && Array.isArray(o.changes) && Array.isArray(o.refusals);
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
// src/lib/report/verify-report.ts
|
|
2316
|
+
var KNOWN_KINDS = new Set(REPORT_ELEMENTS);
|
|
2317
|
+
async function verifyReport(html, sources) {
|
|
2318
|
+
const sanitized = sanitizeReportHtml(html);
|
|
2319
|
+
const parsed = parseElements(sanitized);
|
|
2320
|
+
const exportCache = /* @__PURE__ */ new Map();
|
|
2321
|
+
function getExport(system) {
|
|
2322
|
+
let p = exportCache.get(system);
|
|
2323
|
+
if (!p) {
|
|
2324
|
+
p = sources.runExport(system).then(
|
|
2325
|
+
(data) => ({ ok: true, data }),
|
|
2326
|
+
(err) => ({ ok: false, reason: err instanceof Error ? err.message : String(err) })
|
|
2327
|
+
);
|
|
2328
|
+
exportCache.set(system, p);
|
|
2329
|
+
}
|
|
2330
|
+
return p;
|
|
2331
|
+
}
|
|
2332
|
+
const elements = [];
|
|
2333
|
+
const caseElements = [];
|
|
2334
|
+
const metricParsed = [];
|
|
2335
|
+
async function resolveOne(p) {
|
|
2336
|
+
try {
|
|
2337
|
+
switch (p.kind) {
|
|
2338
|
+
case "sv-ticket": {
|
|
2339
|
+
const r = await resolveTicketElement(sources.archive, p.attrs.ref ?? "");
|
|
2340
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, ...r };
|
|
2341
|
+
}
|
|
2342
|
+
case "sv-approval": {
|
|
2343
|
+
const r = await resolveApprovalElement(sources.archive, p.attrs.ticket ?? "");
|
|
2344
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, ...r };
|
|
2345
|
+
}
|
|
2346
|
+
case "sv-mandate": {
|
|
2347
|
+
const r = await resolveMandateElement(sources.archive, p.attrs.ticket ?? "");
|
|
2348
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, ...r };
|
|
2349
|
+
}
|
|
2350
|
+
case "sv-record": {
|
|
2351
|
+
const system = p.attrs.system;
|
|
2352
|
+
if (system !== "email" && system !== "crm" && system !== "erp") {
|
|
2353
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, status: "unverifiable", reason: `Unknown system "${p.attrs.system ?? ""}".` };
|
|
2354
|
+
}
|
|
2355
|
+
const exp = await getExport(system);
|
|
2356
|
+
if (!exp.ok) {
|
|
2357
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, status: "unverifiable", reason: `Could not read the ${system} simulator export: ${exp.reason}` };
|
|
2358
|
+
}
|
|
2359
|
+
const r = resolveRecord(system, exp.data, p.attrs.ref ?? "");
|
|
2360
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, ...r };
|
|
2361
|
+
}
|
|
2362
|
+
case "sv-case": {
|
|
2363
|
+
const exp = await getExport("email");
|
|
2364
|
+
if (!exp.ok || !isEmailExport(exp.data)) {
|
|
2365
|
+
return {
|
|
2366
|
+
id: p.id,
|
|
2367
|
+
kind: p.kind,
|
|
2368
|
+
attrs: p.attrs,
|
|
2369
|
+
status: "unverifiable",
|
|
2370
|
+
reason: `Could not read the email simulator export: ${!exp.ok ? exp.reason : "unexpected shape"}`
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
const resolution = await resolveCaseElement(sources.archive, exp.data, p.attrs);
|
|
2374
|
+
const el = {
|
|
2375
|
+
id: p.id,
|
|
2376
|
+
kind: p.kind,
|
|
2377
|
+
attrs: p.attrs,
|
|
2378
|
+
status: resolution.status,
|
|
2379
|
+
...resolution.reason ? { reason: resolution.reason } : {},
|
|
2380
|
+
...resolution.data ? { data: resolution.data } : {}
|
|
2381
|
+
};
|
|
2382
|
+
caseElements.push({ el, resolution });
|
|
2383
|
+
return el;
|
|
2384
|
+
}
|
|
2385
|
+
default:
|
|
2386
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, status: "unverifiable", reason: "Unhandled element kind." };
|
|
2387
|
+
}
|
|
2388
|
+
} catch (err) {
|
|
2389
|
+
return { id: p.id, kind: p.kind, attrs: p.attrs, status: "unverifiable", reason: err instanceof Error ? err.message : String(err) };
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
for (const p of parsed) {
|
|
2393
|
+
if (!KNOWN_KINDS.has(p.kind)) {
|
|
2394
|
+
elements.push({
|
|
2395
|
+
id: p.id,
|
|
2396
|
+
kind: p.kind,
|
|
2397
|
+
attrs: p.attrs,
|
|
2398
|
+
status: "unverifiable",
|
|
2399
|
+
reason: `Unknown element "${p.kind}" \u2014 not one of the six elements report-brief.ts defines.`
|
|
2400
|
+
});
|
|
2401
|
+
continue;
|
|
2402
|
+
}
|
|
2403
|
+
if (p.kind === "sv-metric") {
|
|
2404
|
+
metricParsed.push(p);
|
|
2405
|
+
continue;
|
|
2406
|
+
}
|
|
2407
|
+
elements.push(await resolveOne(p));
|
|
2408
|
+
}
|
|
2409
|
+
for (const p of metricParsed) {
|
|
2410
|
+
try {
|
|
2411
|
+
const kind = p.attrs.kind ?? "";
|
|
2412
|
+
const casesAttr = (p.attrs.cases ?? "all").trim();
|
|
2413
|
+
const requestedCount = casesAttr === "all" ? caseElements.length : new Set(casesAttr.split(/\s+/).filter(Boolean)).size;
|
|
2414
|
+
let selected = caseElements.filter((c) => c.resolution.status !== "unverifiable" && c.resolution.data);
|
|
2415
|
+
if (casesAttr !== "all") {
|
|
2416
|
+
const wanted = new Set(casesAttr.split(/\s+/).filter(Boolean));
|
|
2417
|
+
selected = selected.filter((c) => wanted.has(c.resolution.data.caseId));
|
|
2418
|
+
}
|
|
2419
|
+
const caseInputs = selected.map((c) => ({
|
|
2420
|
+
caseId: c.resolution.data.caseId,
|
|
2421
|
+
startTime: c.resolution.data.start.time,
|
|
2422
|
+
goalTime: c.resolution.data.goal.time,
|
|
2423
|
+
totalDurationSeconds: c.resolution.data.totalDurationSeconds,
|
|
2424
|
+
ticketIds: [c.resolution.data.goal.ticketId, ...c.resolution.data.steps.map((s) => s.ticketId)],
|
|
2425
|
+
approvals: c.resolution.data.approvals
|
|
2426
|
+
}));
|
|
2427
|
+
const verifiedCount = caseInputs.length;
|
|
2428
|
+
if (verifiedCount === 0) {
|
|
2429
|
+
elements.push({
|
|
2430
|
+
id: p.id,
|
|
2431
|
+
kind: p.kind,
|
|
2432
|
+
attrs: p.attrs,
|
|
2433
|
+
status: "unverifiable",
|
|
2434
|
+
reason: requestedCount > 0 ? `No verified cases (0 of ${requestedCount} requested) \u2014 no figure.` : "No verified cases \u2014 no figure."
|
|
2435
|
+
});
|
|
2436
|
+
continue;
|
|
2437
|
+
}
|
|
2438
|
+
const refusalTimes = kind === "refusals" ? await collectRefusalTimes(getExport) : [];
|
|
2439
|
+
const value = computeMetric(kind, caseInputs, refusalTimes);
|
|
2440
|
+
const data = { kind, cases: casesAttr, value, caseCount: verifiedCount };
|
|
2441
|
+
if (verifiedCount < requestedCount) {
|
|
2442
|
+
elements.push({
|
|
2443
|
+
id: p.id,
|
|
2444
|
+
kind: p.kind,
|
|
2445
|
+
attrs: p.attrs,
|
|
2446
|
+
status: "warning",
|
|
2447
|
+
reason: `Computed over ${verifiedCount} of ${requestedCount} requested cases \u2014 the rest did not verify.`,
|
|
2448
|
+
data
|
|
2449
|
+
});
|
|
2450
|
+
} else {
|
|
2451
|
+
elements.push({ id: p.id, kind: p.kind, attrs: p.attrs, status: "verified", data });
|
|
2452
|
+
}
|
|
2453
|
+
} catch (err) {
|
|
2454
|
+
elements.push({ id: p.id, kind: p.kind, attrs: p.attrs, status: "unverifiable", reason: err instanceof Error ? err.message : String(err) });
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
const proof = buildProof(elements, caseElements);
|
|
2458
|
+
const coverage = await buildCoverage(caseElements, getExport, sources.archive, proof.ticketsReferenced);
|
|
2459
|
+
return { html: sanitized, elements, proof, coverage };
|
|
2460
|
+
}
|
|
2461
|
+
async function collectRefusalTimes(getExport) {
|
|
2462
|
+
const times = [];
|
|
2463
|
+
for (const system of ["email", "crm", "erp"]) {
|
|
2464
|
+
const exp = await getExport(system);
|
|
2465
|
+
if (!exp.ok) throw new Error(`Could not read the ${system} simulator export: ${exp.reason}`);
|
|
2466
|
+
const refusals = exp.data.refusals ?? [];
|
|
2467
|
+
for (const r of refusals) {
|
|
2468
|
+
const t = parseTimestampSeconds(r.at);
|
|
2469
|
+
if (t !== void 0) times.push(t);
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
return times;
|
|
2473
|
+
}
|
|
2474
|
+
function summarize(el) {
|
|
2475
|
+
const d = el.data ?? {};
|
|
2476
|
+
switch (el.kind) {
|
|
2477
|
+
case "sv-ticket": {
|
|
2478
|
+
const actionLabel = typeof d.actionLabel === "string" ? d.actionLabel : formatActionLabel(d.action);
|
|
2479
|
+
const timeLabel = typeof d.timeLabel === "string" ? d.timeLabel : formatDateTime(d.time);
|
|
2480
|
+
return `${actionLabel} \u2014 ${timeLabel}`;
|
|
2481
|
+
}
|
|
2482
|
+
case "sv-approval": {
|
|
2483
|
+
const who = typeof d.whoLabel === "string" ? d.whoLabel : d.who?.join(", ") ?? "unknown";
|
|
2484
|
+
const waitLabel = typeof d.waitLabel === "string" ? d.waitLabel : typeof d.waitSeconds === "number" ? formatDuration(d.waitSeconds) : "unknown";
|
|
2485
|
+
return `Approval by ${who} \u2014 waited ${waitLabel}`;
|
|
2486
|
+
}
|
|
2487
|
+
case "sv-mandate": {
|
|
2488
|
+
const profileLabel = typeof d.profileLabel === "string" ? d.profileLabel : profileShortLabel(typeof d.profile === "string" ? d.profile : void 0);
|
|
2489
|
+
const owners = Array.isArray(d.owners) && d.owners.length > 0 ? d.owners.join(", ") : "unknown owner";
|
|
2490
|
+
return `Mandate: ${profileLabel} \xB7 ${owners}`;
|
|
2491
|
+
}
|
|
2492
|
+
case "sv-record":
|
|
2493
|
+
return `${String(d.kind ?? "record")} record checked`;
|
|
2494
|
+
case "sv-case": {
|
|
2495
|
+
const steps = d.steps?.length ?? 0;
|
|
2496
|
+
return `Case ${String(d.caseId)}: ${steps} ${steps === 1 ? "step" : "steps"}, ${formatDuration(d.totalDurationSeconds)}`;
|
|
2497
|
+
}
|
|
2498
|
+
case "sv-metric": {
|
|
2499
|
+
const kind = String(d.kind ?? "");
|
|
2500
|
+
const label = METRIC_LABELS[kind] ?? kind;
|
|
2501
|
+
return `${label} = ${formatMetricValue(kind, d.value)}`;
|
|
2502
|
+
}
|
|
2503
|
+
default:
|
|
2504
|
+
return el.id;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
function buildProof(elements, caseElements) {
|
|
2508
|
+
const ticketsReferenced = /* @__PURE__ */ new Set();
|
|
2509
|
+
const signaturesValid = /* @__PURE__ */ new Set();
|
|
2510
|
+
let recordsChecked = 0;
|
|
2511
|
+
let unverifiableCount = 0;
|
|
2512
|
+
const verifiedValues = [];
|
|
2513
|
+
for (const el of elements) {
|
|
2514
|
+
if (el.status === "unverifiable") unverifiableCount++;
|
|
2515
|
+
if (el.status === "verified" || el.status === "warning") {
|
|
2516
|
+
verifiedValues.push({ elementId: el.id, kind: el.kind, summary: summarize(el) });
|
|
2517
|
+
}
|
|
2518
|
+
if (el.kind === "sv-ticket" && el.attrs.ref) {
|
|
2519
|
+
ticketsReferenced.add(el.attrs.ref);
|
|
2520
|
+
if (el.status !== "unverifiable") signaturesValid.add(el.attrs.ref);
|
|
2521
|
+
}
|
|
2522
|
+
if ((el.kind === "sv-approval" || el.kind === "sv-mandate") && el.attrs.ticket) {
|
|
2523
|
+
ticketsReferenced.add(el.attrs.ticket);
|
|
2524
|
+
if (el.status !== "unverifiable") signaturesValid.add(el.attrs.ticket);
|
|
2525
|
+
}
|
|
2526
|
+
if (el.kind === "sv-record" && el.status === "verified") {
|
|
2527
|
+
recordsChecked++;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
for (const { el, resolution } of caseElements) {
|
|
2531
|
+
const { goalId, stepIds } = parseCaseAttrs(el.attrs);
|
|
2532
|
+
for (const id of [goalId, ...stepIds]) {
|
|
2533
|
+
if (!id) continue;
|
|
2534
|
+
ticketsReferenced.add(id);
|
|
2535
|
+
if (resolution.status !== "unverifiable") signaturesValid.add(id);
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
return {
|
|
2539
|
+
ticketsReferenced: [...ticketsReferenced],
|
|
2540
|
+
signaturesValid: signaturesValid.size,
|
|
2541
|
+
recordsChecked,
|
|
2542
|
+
unverifiableCount,
|
|
2543
|
+
verifiedValues
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
async function buildCoverage(caseElements, getExport, archive, referenced) {
|
|
2547
|
+
const exp = await getExport("email");
|
|
2548
|
+
const emailExportError = !exp.ok ? exp.reason : !isEmailExport(exp.data) ? "Email simulator export had an unexpected shape." : void 0;
|
|
2549
|
+
const loadedCases = exp.ok && isEmailExport(exp.data) ? [...new Set(exp.data.inbox.map((m) => m.case_id).filter((c) => !!c))] : [];
|
|
2550
|
+
const covered = new Set(
|
|
2551
|
+
caseElements.map((c) => c.resolution.startCaseId).filter((c) => !!c)
|
|
2552
|
+
);
|
|
2553
|
+
const coveredCases = loadedCases.filter((c) => covered.has(c));
|
|
2554
|
+
const missingCases = loadedCases.filter((c) => !covered.has(c));
|
|
2555
|
+
const loadedAt = exp.ok && isEmailExport(exp.data) ? exp.data.simulation_load?.loaded_at : void 0;
|
|
2556
|
+
const periodStart = loadedAt !== void 0 ? parseTimestampSeconds(loadedAt) ?? null : null;
|
|
2557
|
+
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);
|
|
2558
|
+
const referencedSet = new Set(referenced);
|
|
2559
|
+
const ticketsReferenced = inPeriod.filter((id) => referencedSet.has(id));
|
|
2560
|
+
const ticketsNotReferenced = inPeriod.filter((id) => !referencedSet.has(id));
|
|
2561
|
+
return {
|
|
2562
|
+
...emailExportError ? { emailExportError } : {},
|
|
2563
|
+
loadedCases,
|
|
2564
|
+
coveredCases,
|
|
2565
|
+
missingCases,
|
|
2566
|
+
periodStart,
|
|
2567
|
+
ticketsInPeriod: inPeriod,
|
|
2568
|
+
ticketsReferenced,
|
|
2569
|
+
ticketsNotReferenced
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
// src/lib/report/report-store.ts
|
|
2574
|
+
var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ?? join10(homedir7(), ".suveren");
|
|
2575
|
+
var ReportStore = class {
|
|
2576
|
+
report = null;
|
|
2577
|
+
baseDir;
|
|
2578
|
+
vaultKey = null;
|
|
2579
|
+
constructor(filePath) {
|
|
2580
|
+
if (filePath && filePath.endsWith(".json")) {
|
|
2581
|
+
this.baseDir = dirname8(filePath);
|
|
2582
|
+
} else {
|
|
2583
|
+
this.baseDir = filePath ?? DEFAULT_DIR7;
|
|
2584
|
+
}
|
|
2585
|
+
this.loadPlaintext();
|
|
2586
|
+
}
|
|
2587
|
+
/** Set the vault key for encryption. Triggers migration from plaintext if needed. */
|
|
2588
|
+
setVaultKey(key) {
|
|
2589
|
+
this.vaultKey = key;
|
|
2590
|
+
if (existsSync10(this.encryptedFilePath)) {
|
|
2591
|
+
this.loadEncrypted();
|
|
2592
|
+
} else if (this.report !== null) {
|
|
2593
|
+
this.persistEncrypted();
|
|
2594
|
+
if (existsSync10(this.plaintextFilePath)) {
|
|
2595
|
+
try {
|
|
2596
|
+
unlinkSync5(this.plaintextFilePath);
|
|
2597
|
+
} catch {
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
/**
|
|
2603
|
+
* True when encrypted evidence exists on disk that this process cannot
|
|
2604
|
+
* read yet (no vault key). Callers MUST NOT present the store as "no
|
|
2605
|
+
* report yet" in this state — that would claim a different fact than
|
|
2606
|
+
* "the report exists but is locked".
|
|
2607
|
+
*/
|
|
2608
|
+
isLocked() {
|
|
2609
|
+
return this.vaultKey === null && existsSync10(this.encryptedFilePath);
|
|
2610
|
+
}
|
|
2611
|
+
/** The stored, already-verified report — or null if none has been saved
|
|
2612
|
+
* (and the store is not locked; check `isLocked()` first). */
|
|
2613
|
+
getReport() {
|
|
2614
|
+
return this.report;
|
|
2615
|
+
}
|
|
2616
|
+
/**
|
|
2617
|
+
* Replace the current report with `html`, verifying it ONCE against
|
|
2618
|
+
* `sources` before storing. Always replaces — there is only ever one
|
|
2619
|
+
* current report (plan decision).
|
|
2620
|
+
*/
|
|
2621
|
+
async saveReport(html, sources, now = Math.floor(Date.now() / 1e3)) {
|
|
2622
|
+
const result = await verifyReport(html, sources);
|
|
2623
|
+
this.report = { html: result.html, savedAt: now, checkedAt: now, result };
|
|
2624
|
+
this.persist();
|
|
2625
|
+
return this.report;
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* Re-run verification against the CURRENTLY stored report's html — "Check
|
|
2629
|
+
* again" in the UI. Does not change `savedAt` (the AI did not rewrite
|
|
2630
|
+
* anything); only `checkedAt` and `result` move. Returns null if there is
|
|
2631
|
+
* no report to recheck.
|
|
2632
|
+
*/
|
|
2633
|
+
async recheck(sources, now = Math.floor(Date.now() / 1e3)) {
|
|
2634
|
+
if (!this.report) return null;
|
|
2635
|
+
const result = await verifyReport(this.report.html, sources);
|
|
2636
|
+
this.report = { ...this.report, checkedAt: now, result };
|
|
2637
|
+
this.persist();
|
|
2638
|
+
return this.report;
|
|
2639
|
+
}
|
|
2640
|
+
// ─── Encryption helpers ─────────────────────────────────────────────────
|
|
2641
|
+
encrypt(plaintext) {
|
|
2642
|
+
if (!this.vaultKey) throw new Error("No vault key");
|
|
2643
|
+
const iv = randomBytes5(12);
|
|
2644
|
+
const cipher = createCipheriv5("aes-256-gcm", this.vaultKey, iv);
|
|
2645
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
2646
|
+
const tag = cipher.getAuthTag();
|
|
2647
|
+
return { iv: iv.toString("hex"), ciphertext: encrypted.toString("hex"), tag: tag.toString("hex") };
|
|
2648
|
+
}
|
|
2649
|
+
decrypt(blob) {
|
|
2650
|
+
if (!this.vaultKey) throw new Error("No vault key");
|
|
2651
|
+
const decipher = createDecipheriv5("aes-256-gcm", this.vaultKey, Buffer.from(blob.iv, "hex"));
|
|
2652
|
+
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
2653
|
+
const decrypted = Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]);
|
|
2654
|
+
return decrypted.toString("utf8");
|
|
2655
|
+
}
|
|
2656
|
+
// ─── File paths ─────────────────────────────────────────────────────────
|
|
2657
|
+
get plaintextFilePath() {
|
|
2658
|
+
return join10(this.baseDir, "report.json");
|
|
2659
|
+
}
|
|
2660
|
+
get encryptedFilePath() {
|
|
2661
|
+
return join10(this.baseDir, "report.enc.json");
|
|
2662
|
+
}
|
|
2663
|
+
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
2664
|
+
loadPlaintext() {
|
|
2665
|
+
if (!existsSync10(this.plaintextFilePath)) {
|
|
2666
|
+
mkdirSync8(this.baseDir, { recursive: true });
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
try {
|
|
2670
|
+
const raw = readFileSync11(this.plaintextFilePath, "utf-8");
|
|
2671
|
+
const parsed = JSON.parse(raw);
|
|
2672
|
+
this.report = parsed.report ?? null;
|
|
2673
|
+
} catch {
|
|
2674
|
+
console.error(`[ReportStore] Could not parse ${this.plaintextFilePath} \u2014 preserving as .corrupt`);
|
|
2675
|
+
try {
|
|
2676
|
+
writeFileSync8(`${this.plaintextFilePath}.corrupt-${Date.now()}`, readFileSync11(this.plaintextFilePath));
|
|
2677
|
+
} catch {
|
|
2678
|
+
}
|
|
2679
|
+
this.report = null;
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
loadEncrypted() {
|
|
2683
|
+
if (!existsSync10(this.encryptedFilePath)) return;
|
|
2684
|
+
try {
|
|
2685
|
+
const raw = readFileSync11(this.encryptedFilePath, "utf-8");
|
|
2686
|
+
const data = JSON.parse(raw);
|
|
2687
|
+
const decrypted = this.decrypt(data.blob);
|
|
2688
|
+
const parsed = JSON.parse(decrypted);
|
|
2689
|
+
this.report = parsed.report ?? null;
|
|
2690
|
+
} catch (err) {
|
|
2691
|
+
console.error(`[ReportStore] Could not decrypt ${this.encryptedFilePath} \u2014 preserving as .corrupt:`, err);
|
|
2692
|
+
try {
|
|
2693
|
+
writeFileSync8(`${this.encryptedFilePath}.corrupt-${Date.now()}`, readFileSync11(this.encryptedFilePath));
|
|
2694
|
+
} catch {
|
|
2695
|
+
}
|
|
2696
|
+
this.report = null;
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
persist() {
|
|
2700
|
+
if (this.vaultKey) {
|
|
2701
|
+
this.persistEncrypted();
|
|
2702
|
+
} else {
|
|
2703
|
+
this.persistPlaintext();
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
persistPlaintext() {
|
|
2707
|
+
const data = { version: 1, report: this.report };
|
|
2708
|
+
mkdirSync8(this.baseDir, { recursive: true, mode: 448 });
|
|
2709
|
+
writeFileSync8(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2710
|
+
}
|
|
2711
|
+
persistEncrypted() {
|
|
2712
|
+
const data = {
|
|
2713
|
+
version: 1,
|
|
2714
|
+
blob: this.encrypt(JSON.stringify({ version: 1, report: this.report }))
|
|
2715
|
+
};
|
|
2716
|
+
mkdirSync8(this.baseDir, { recursive: true, mode: 448 });
|
|
2717
|
+
writeFileSync8(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2718
|
+
}
|
|
2719
|
+
};
|
|
2720
|
+
|
|
2091
2721
|
// src/lib/shared-state.ts
|
|
2092
2722
|
var SharedState = class {
|
|
2093
2723
|
spClient;
|
|
@@ -2096,6 +2726,9 @@ var SharedState = class {
|
|
|
2096
2726
|
executionLog;
|
|
2097
2727
|
denialLog;
|
|
2098
2728
|
receiptArchive;
|
|
2729
|
+
/** The one current evidence-backed report the user's AI has written —
|
|
2730
|
+
* see report/report-store.ts. Vault-encrypted, like the stores above. */
|
|
2731
|
+
reportStore;
|
|
2099
2732
|
/** Which tickets this Gatekeeper has executed — the "one execution per ticket" half of exactly-once. */
|
|
2100
2733
|
executionJournal;
|
|
2101
2734
|
/** What THIS gateway submitted for each review-mode proposal it created —
|
|
@@ -2116,6 +2749,7 @@ var SharedState = class {
|
|
|
2116
2749
|
this.executionLog = new ExecutionLog(gateStorePath);
|
|
2117
2750
|
this.denialLog = new DenialLog(gateStorePath);
|
|
2118
2751
|
this.receiptArchive = new ReceiptArchive(gateStorePath);
|
|
2752
|
+
this.reportStore = new ReportStore(gateStorePath);
|
|
2119
2753
|
this.executionJournal = new ExecutionJournal(gateStorePath);
|
|
2120
2754
|
this.proposalSubmissions = new ProposalSubmissionStore(gateStorePath);
|
|
2121
2755
|
this.gatekeeper = new MCPGatekeeper(this.cache);
|
|
@@ -2271,12 +2905,12 @@ function formatConsumptionFull(entries) {
|
|
|
2271
2905
|
|
|
2272
2906
|
// src/lib/context-loader.ts
|
|
2273
2907
|
import { readFileSync as readFileSync12, existsSync as existsSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, renameSync as renameSync3 } from "fs";
|
|
2274
|
-
import { homedir as
|
|
2908
|
+
import { homedir as homedir8 } from "os";
|
|
2275
2909
|
import { join as join11, dirname as dirname9 } from "path";
|
|
2276
|
-
var
|
|
2910
|
+
var DEFAULT_DIR8 = process.env.SUVEREN_DATA_DIR ?? join11(homedir8(), ".suveren");
|
|
2277
2911
|
var CONTEXT_MAX_BYTES = 16 * 1024;
|
|
2278
2912
|
function contextFilePath(dataDir2) {
|
|
2279
|
-
return join11(dataDir2 ??
|
|
2913
|
+
return join11(dataDir2 ?? DEFAULT_DIR8, "context.md");
|
|
2280
2914
|
}
|
|
2281
2915
|
function writeContextFile(content, dataDir2) {
|
|
2282
2916
|
const bytes = Buffer.byteLength(content, "utf-8");
|
|
@@ -2381,8 +3015,8 @@ var FOOTER_RE = /\n*(?:—|--) (?:Sent|Published) by (?:an AI agent|[^\n]+?'s AI
|
|
|
2381
3015
|
function shouldAttachFooter() {
|
|
2382
3016
|
return true;
|
|
2383
3017
|
}
|
|
2384
|
-
function receiptUrl(
|
|
2385
|
-
return `${AS_BASE}/r/${
|
|
3018
|
+
function receiptUrl(receiptId2) {
|
|
3019
|
+
return `${AS_BASE}/r/${receiptId2}`;
|
|
2386
3020
|
}
|
|
2387
3021
|
function verbForProfile(profile) {
|
|
2388
3022
|
return profile === "publish" ? "Published" : "Sent";
|
|
@@ -2390,11 +3024,11 @@ function verbForProfile(profile) {
|
|
|
2390
3024
|
function hapLine() {
|
|
2391
3025
|
return `-- ${OPERATOR_NAME} implements HAP, the open Human Agency Protocol: ${HAP_URL}`;
|
|
2392
3026
|
}
|
|
2393
|
-
function footerText(
|
|
3027
|
+
function footerText(receiptId2, verb, subject) {
|
|
2394
3028
|
const identity = deriveIdentityLine(subject, { operatorName: OPERATOR_NAME }).replace(/^Sent\b/, verb);
|
|
2395
3029
|
return `
|
|
2396
3030
|
|
|
2397
|
-
-- ${identity}. Receipt: ${receiptUrl(
|
|
3031
|
+
-- ${identity}. Receipt: ${receiptUrl(receiptId2)}
|
|
2398
3032
|
|
|
2399
3033
|
${hapLine()}`;
|
|
2400
3034
|
}
|
|
@@ -2413,13 +3047,13 @@ function detectContentField(tool) {
|
|
|
2413
3047
|
function stripFooter(value) {
|
|
2414
3048
|
return value.replace(FOOTER_RE, "").replace(/\n+$/, "");
|
|
2415
3049
|
}
|
|
2416
|
-
function appendVerificationFooter(tool, args,
|
|
3050
|
+
function appendVerificationFooter(tool, args, receiptId2, subject) {
|
|
2417
3051
|
const profile = tool.gating?.profile;
|
|
2418
3052
|
if (!profile || !FOOTER_PROFILES.has(profile)) return args;
|
|
2419
3053
|
const field = detectContentField(tool);
|
|
2420
3054
|
if (!field) return args;
|
|
2421
3055
|
const current = typeof args[field] === "string" ? args[field] : "";
|
|
2422
|
-
return { ...args, [field]: stripFooter(current) + footerText(
|
|
3056
|
+
return { ...args, [field]: stripFooter(current) + footerText(receiptId2, verbForProfile(profile), subject) };
|
|
2423
3057
|
}
|
|
2424
3058
|
|
|
2425
3059
|
// src/lib/content-binding.ts
|
|
@@ -2472,10 +3106,10 @@ function computeContentBinding(profileId, tool, toolArgs, actionType) {
|
|
|
2472
3106
|
boundContent
|
|
2473
3107
|
};
|
|
2474
3108
|
}
|
|
2475
|
-
function attachReceiptId(tool, args,
|
|
3109
|
+
function attachReceiptId(tool, args, receiptId2) {
|
|
2476
3110
|
const schema = tool.inputSchema;
|
|
2477
3111
|
if (!schema?.properties || !("receipt_id" in schema.properties)) return args;
|
|
2478
|
-
return { ...args, receipt_id:
|
|
3112
|
+
return { ...args, receipt_id: receiptId2 };
|
|
2479
3113
|
}
|
|
2480
3114
|
|
|
2481
3115
|
// src/lib/ticket-verify.ts
|
|
@@ -3365,7 +3999,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
3365
3999
|
};
|
|
3366
4000
|
}
|
|
3367
4001
|
}
|
|
3368
|
-
let
|
|
4002
|
+
let receiptId2;
|
|
3369
4003
|
try {
|
|
3370
4004
|
const actionType = declaredActionType;
|
|
3371
4005
|
const binding = computeContentBinding(auth.profileId, tool, args, actionType);
|
|
@@ -3387,7 +4021,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
3387
4021
|
// field by field rather than spread.
|
|
3388
4022
|
...binding ? { contentHash: binding.contentHash, contentBinding: binding.contentBinding } : {}
|
|
3389
4023
|
});
|
|
3390
|
-
|
|
4024
|
+
receiptId2 = typeof receipt?.id === "string" ? receipt.id : void 0;
|
|
3391
4025
|
await verifyTicket(state2.cache, receipt, {
|
|
3392
4026
|
action: tool.namespacedName,
|
|
3393
4027
|
executionContext: { ...execution },
|
|
@@ -3541,12 +4175,12 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.`
|
|
|
3541
4175
|
execution: { ...execution },
|
|
3542
4176
|
timestamp: Math.floor(Date.now() / 1e3)
|
|
3543
4177
|
});
|
|
3544
|
-
let outgoingArgs = shouldAttachFooter() &&
|
|
3545
|
-
if (
|
|
4178
|
+
let outgoingArgs = shouldAttachFooter() && receiptId2 ? appendVerificationFooter(tool, args, receiptId2, auth.subjects?.[0]) : args;
|
|
4179
|
+
if (receiptId2) outgoingArgs = attachReceiptId(tool, outgoingArgs, receiptId2);
|
|
3546
4180
|
outgoingArgs = encodeOutgoingArgs(tool, outgoingArgs);
|
|
3547
|
-
if (
|
|
4181
|
+
if (receiptId2) {
|
|
3548
4182
|
const begun = state2.executionJournal.begin({
|
|
3549
|
-
ticketId:
|
|
4183
|
+
ticketId: receiptId2,
|
|
3550
4184
|
tool: tool.namespacedName,
|
|
3551
4185
|
argsHash: hashToolArgs(args)
|
|
3552
4186
|
});
|
|
@@ -3554,17 +4188,17 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.`
|
|
|
3554
4188
|
return {
|
|
3555
4189
|
content: [{
|
|
3556
4190
|
type: "text",
|
|
3557
|
-
text: `Blocked: ticket ${
|
|
4191
|
+
text: `Blocked: ticket ${receiptId2} was already used to execute ${tool.namespacedName} (state: ${begun.existing.state}). Not running it again.`
|
|
3558
4192
|
}],
|
|
3559
4193
|
isError: true
|
|
3560
4194
|
};
|
|
3561
4195
|
}
|
|
3562
4196
|
try {
|
|
3563
4197
|
const result = await integrationManager2.callTool(tool.integrationId, tool.originalName, outgoingArgs);
|
|
3564
|
-
state2.executionJournal.complete(
|
|
4198
|
+
state2.executionJournal.complete(receiptId2, "done");
|
|
3565
4199
|
return result;
|
|
3566
4200
|
} catch (err) {
|
|
3567
|
-
state2.executionJournal.complete(
|
|
4201
|
+
state2.executionJournal.complete(receiptId2, "failed");
|
|
3568
4202
|
throw err;
|
|
3569
4203
|
}
|
|
3570
4204
|
}
|
|
@@ -4045,7 +4679,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
4045
4679
|
};
|
|
4046
4680
|
}
|
|
4047
4681
|
}
|
|
4048
|
-
let
|
|
4682
|
+
let receiptId2;
|
|
4049
4683
|
let replayed = false;
|
|
4050
4684
|
try {
|
|
4051
4685
|
const binding = computeContentBinding(
|
|
@@ -4078,7 +4712,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
4078
4712
|
// this machine — see the archive call below.
|
|
4079
4713
|
...binding ? { contentHash: binding.contentHash, contentBinding: binding.contentBinding } : {}
|
|
4080
4714
|
});
|
|
4081
|
-
|
|
4715
|
+
receiptId2 = typeof receipt?.id === "string" ? receipt.id : void 0;
|
|
4082
4716
|
replayed = idempotent;
|
|
4083
4717
|
await verifyTicket(state2.cache, receipt, {
|
|
4084
4718
|
action: proposal.tool,
|
|
@@ -4133,14 +4767,14 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
4133
4767
|
isError: true
|
|
4134
4768
|
};
|
|
4135
4769
|
}
|
|
4136
|
-
if (!
|
|
4770
|
+
if (!receiptId2) {
|
|
4137
4771
|
return {
|
|
4138
4772
|
text: `Proposal ${proposal.id}: the ticket carries no id \u2014 refusing to execute an action that could not be recorded.`,
|
|
4139
4773
|
isError: true
|
|
4140
4774
|
};
|
|
4141
4775
|
}
|
|
4142
4776
|
const begun = state2.executionJournal.begin({
|
|
4143
|
-
ticketId:
|
|
4777
|
+
ticketId: receiptId2,
|
|
4144
4778
|
proposalId: proposal.id,
|
|
4145
4779
|
tool: proposal.tool,
|
|
4146
4780
|
argsHash: hashToolArgs(proposal.toolArgs)
|
|
@@ -4150,36 +4784,36 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
4150
4784
|
const when = new Date(prior.startedAt * 1e3).toISOString();
|
|
4151
4785
|
if (prior.state === "done") {
|
|
4152
4786
|
return {
|
|
4153
|
-
text: `Proposal ${proposal.id} was already executed under ticket ${
|
|
4787
|
+
text: `Proposal ${proposal.id} was already executed under ticket ${receiptId2} at ${when}. Not running it again.`
|
|
4154
4788
|
};
|
|
4155
4789
|
}
|
|
4156
4790
|
return {
|
|
4157
|
-
text: `Proposal ${proposal.id}: an execution under ticket ${
|
|
4791
|
+
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
4792
|
isError: true
|
|
4159
4793
|
};
|
|
4160
4794
|
}
|
|
4161
4795
|
if (replayed) {
|
|
4162
4796
|
console.error(
|
|
4163
|
-
`[Suveren MCP] Proposal ${proposal.id}: ticket ${
|
|
4797
|
+
`[Suveren MCP] Proposal ${proposal.id}: ticket ${receiptId2} was replayed by the AS with no local execution record \u2014 executing once (lost-response recovery).`
|
|
4164
4798
|
);
|
|
4165
4799
|
}
|
|
4166
4800
|
try {
|
|
4167
4801
|
let outgoingArgs = proposal.toolArgs;
|
|
4168
|
-
if (discovered &&
|
|
4802
|
+
if (discovered && receiptId2) {
|
|
4169
4803
|
if (shouldAttachFooter()) {
|
|
4170
|
-
outgoingArgs = appendVerificationFooter(discovered, outgoingArgs,
|
|
4804
|
+
outgoingArgs = appendVerificationFooter(discovered, outgoingArgs, receiptId2, cachedAuth?.subjects?.[0]);
|
|
4171
4805
|
}
|
|
4172
|
-
outgoingArgs = attachReceiptId(discovered, outgoingArgs,
|
|
4806
|
+
outgoingArgs = attachReceiptId(discovered, outgoingArgs, receiptId2);
|
|
4173
4807
|
outgoingArgs = encodeOutgoingArgs(discovered, outgoingArgs);
|
|
4174
4808
|
}
|
|
4175
4809
|
let result;
|
|
4176
4810
|
try {
|
|
4177
4811
|
result = await integrationManager2.callTool(integrationId, toolName, outgoingArgs);
|
|
4178
4812
|
} catch (err) {
|
|
4179
|
-
state2.executionJournal.complete(
|
|
4813
|
+
state2.executionJournal.complete(receiptId2, "failed");
|
|
4180
4814
|
throw err;
|
|
4181
4815
|
}
|
|
4182
|
-
state2.executionJournal.complete(
|
|
4816
|
+
state2.executionJournal.complete(receiptId2, "done");
|
|
4183
4817
|
state2.executionLog.record({
|
|
4184
4818
|
profileId: proposal.profileId,
|
|
4185
4819
|
path: proposal.path,
|
|
@@ -4512,9 +5146,9 @@ function verifyGateContentHashes(content, auth) {
|
|
|
4512
5146
|
|
|
4513
5147
|
// src/lib/integration-registry.ts
|
|
4514
5148
|
import { mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync12 } from "fs";
|
|
4515
|
-
import { homedir as
|
|
5149
|
+
import { homedir as homedir9 } from "os";
|
|
4516
5150
|
import { dirname as dirname10, join as join12 } from "path";
|
|
4517
|
-
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join12(
|
|
5151
|
+
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join12(homedir9(), ".suveren");
|
|
4518
5152
|
var IntegrationRegistry = class {
|
|
4519
5153
|
integrations = /* @__PURE__ */ new Map();
|
|
4520
5154
|
filePath;
|
|
@@ -4591,7 +5225,7 @@ var IntegrationRegistry = class {
|
|
|
4591
5225
|
};
|
|
4592
5226
|
|
|
4593
5227
|
// src/lib/integration-manager.ts
|
|
4594
|
-
import { homedir as
|
|
5228
|
+
import { homedir as homedir10 } from "os";
|
|
4595
5229
|
import { join as join14, delimiter } from "path";
|
|
4596
5230
|
import { existsSync as existsSync14, writeFileSync as writeFileSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync15, rmSync } from "fs";
|
|
4597
5231
|
import { execFile } from "child_process";
|
|
@@ -4730,7 +5364,7 @@ function safeHost(url) {
|
|
|
4730
5364
|
}
|
|
4731
5365
|
|
|
4732
5366
|
// src/lib/integration-manager.ts
|
|
4733
|
-
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join14(
|
|
5367
|
+
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join14(homedir10(), ".suveren");
|
|
4734
5368
|
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join14(DEFAULT_DATA_DIR2, "integrations");
|
|
4735
5369
|
var OFFLINE_MODE = process.env.SUVEREN_OFFLINE === "1";
|
|
4736
5370
|
var installLock = Promise.resolve();
|
|
@@ -4740,6 +5374,9 @@ function withInstallLock(task) {
|
|
|
4740
5374
|
return run;
|
|
4741
5375
|
}
|
|
4742
5376
|
var INTEGRATIONS_BIN = join14(INTEGRATIONS_DIR, "node_modules", ".bin");
|
|
5377
|
+
function getIntegrationsBinDir() {
|
|
5378
|
+
return INTEGRATIONS_BIN;
|
|
5379
|
+
}
|
|
4743
5380
|
function buildPath() {
|
|
4744
5381
|
const base = process.env.PATH ?? "";
|
|
4745
5382
|
return [INTEGRATIONS_BIN, base].join(delimiter);
|
|
@@ -5561,6 +6198,420 @@ var IntegrationManager = class {
|
|
|
5561
6198
|
}
|
|
5562
6199
|
};
|
|
5563
6200
|
|
|
6201
|
+
// src/lib/report/connector-export.ts
|
|
6202
|
+
import { execFile as execFile2 } from "child_process";
|
|
6203
|
+
import { delimiter as delimiter2 } from "path";
|
|
6204
|
+
var DEFAULT_BIN_NAMES = {
|
|
6205
|
+
email: "email-mcp",
|
|
6206
|
+
crm: "crm-mcp",
|
|
6207
|
+
erp: "erp-mcp"
|
|
6208
|
+
};
|
|
6209
|
+
var ConnectorExportError = class extends Error {
|
|
6210
|
+
constructor(message, system, cause) {
|
|
6211
|
+
super(message);
|
|
6212
|
+
this.system = system;
|
|
6213
|
+
this.cause = cause;
|
|
6214
|
+
this.name = "ConnectorExportError";
|
|
6215
|
+
}
|
|
6216
|
+
};
|
|
6217
|
+
function execFileAsync(command, args, options) {
|
|
6218
|
+
return new Promise((resolve3, reject) => {
|
|
6219
|
+
execFile2(command, args, options, (err, stdout, stderr) => {
|
|
6220
|
+
if (err) reject(Object.assign(err, { stdout, stderr }));
|
|
6221
|
+
else resolve3({ stdout, stderr });
|
|
6222
|
+
});
|
|
6223
|
+
});
|
|
6224
|
+
}
|
|
6225
|
+
function createConnectorExportRunner(config) {
|
|
6226
|
+
return async (system) => {
|
|
6227
|
+
const bin = config.binNames?.[system] ?? DEFAULT_BIN_NAMES[system];
|
|
6228
|
+
const env = {
|
|
6229
|
+
...process.env,
|
|
6230
|
+
PATH: [config.integrationsBinDir, process.env.PATH ?? ""].join(delimiter2),
|
|
6231
|
+
HAP_DATA_DIR: config.dataDir
|
|
6232
|
+
};
|
|
6233
|
+
let stdout;
|
|
6234
|
+
try {
|
|
6235
|
+
const result = await execFileAsync(bin, ["export"], {
|
|
6236
|
+
env,
|
|
6237
|
+
timeout: config.timeoutMs ?? 15e3,
|
|
6238
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
6239
|
+
shell: process.platform === "win32"
|
|
6240
|
+
});
|
|
6241
|
+
stdout = result.stdout;
|
|
6242
|
+
} catch (err) {
|
|
6243
|
+
throw new ConnectorExportError(
|
|
6244
|
+
`${bin} export failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
6245
|
+
system,
|
|
6246
|
+
err
|
|
6247
|
+
);
|
|
6248
|
+
}
|
|
6249
|
+
try {
|
|
6250
|
+
return JSON.parse(stdout);
|
|
6251
|
+
} catch (err) {
|
|
6252
|
+
throw new ConnectorExportError(
|
|
6253
|
+
`${bin} export did not print valid JSON on stdout`,
|
|
6254
|
+
system,
|
|
6255
|
+
err
|
|
6256
|
+
);
|
|
6257
|
+
}
|
|
6258
|
+
};
|
|
6259
|
+
}
|
|
6260
|
+
|
|
6261
|
+
// src/lib/report/render-report.ts
|
|
6262
|
+
import { Parser } from "htmlparser2";
|
|
6263
|
+
function findElementSpans(html) {
|
|
6264
|
+
const spans = [];
|
|
6265
|
+
const seenCount = /* @__PURE__ */ new Map();
|
|
6266
|
+
const openStack = [];
|
|
6267
|
+
let parserRef;
|
|
6268
|
+
const parser = new Parser(
|
|
6269
|
+
{
|
|
6270
|
+
onparserinit(p) {
|
|
6271
|
+
parserRef = p;
|
|
6272
|
+
},
|
|
6273
|
+
onopentag(name) {
|
|
6274
|
+
if (!name.startsWith("sv-") || !parserRef) return;
|
|
6275
|
+
const n = seenCount.get(name) ?? 0;
|
|
6276
|
+
seenCount.set(name, n + 1);
|
|
6277
|
+
openStack.push({ tag: name, id: `${name}-${n}`, start: parserRef.startIndex });
|
|
6278
|
+
},
|
|
6279
|
+
onclosetag(name) {
|
|
6280
|
+
if (!name.startsWith("sv-") || !parserRef) return;
|
|
6281
|
+
const idx = [...openStack].reverse().findIndex((o) => o.tag === name);
|
|
6282
|
+
if (idx === -1) return;
|
|
6283
|
+
const realIdx = openStack.length - 1 - idx;
|
|
6284
|
+
const [open] = openStack.splice(realIdx, 1);
|
|
6285
|
+
spans.push({ id: open.id, kind: open.tag, start: open.start, end: parserRef.endIndex + 1 });
|
|
6286
|
+
}
|
|
6287
|
+
},
|
|
6288
|
+
{ decodeEntities: true, recognizeSelfClosing: true }
|
|
6289
|
+
);
|
|
6290
|
+
parser.write(html);
|
|
6291
|
+
parser.end();
|
|
6292
|
+
return spans.sort((a, b) => a.start - b.start);
|
|
6293
|
+
}
|
|
6294
|
+
function escapeHtml(value) {
|
|
6295
|
+
return String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6296
|
+
}
|
|
6297
|
+
function detailLink(id, ticketId) {
|
|
6298
|
+
const qs = ticketId ? `?element=${encodeURIComponent(id)}&ticket=${encodeURIComponent(ticketId)}` : `?element=${encodeURIComponent(id)}`;
|
|
6299
|
+
return `/reports${qs}`;
|
|
6300
|
+
}
|
|
6301
|
+
function badge(status, okLabel) {
|
|
6302
|
+
if (status === "verified") return `<span class="sv-badge sv-badge-ok">\u2713 ${escapeHtml(okLabel)}</span>`;
|
|
6303
|
+
if (status === "warning") return `<span class="sv-badge sv-badge-warn">\u26A0 verified with a note</span>`;
|
|
6304
|
+
return `<span class="sv-badge sv-badge-bad">\u2717 not verifiable</span>`;
|
|
6305
|
+
}
|
|
6306
|
+
function unverifiableCard(el) {
|
|
6307
|
+
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>`;
|
|
6308
|
+
}
|
|
6309
|
+
function renderTicket(el, interactive) {
|
|
6310
|
+
if (el.status === "unverifiable" || !el.data) return unverifiableCard(el);
|
|
6311
|
+
const d = el.data;
|
|
6312
|
+
const href = typeof d.checkUrl === "string" ? d.checkUrl : void 0;
|
|
6313
|
+
const actionLabel = typeof d.actionLabel === "string" ? d.actionLabel : formatActionLabel(d.action);
|
|
6314
|
+
const timeLabel = typeof d.timeLabel === "string" ? d.timeLabel : formatDateTime(d.time);
|
|
6315
|
+
const profileLabel = typeof d.profileLabel === "string" ? d.profileLabel : escapeHtml(d.profile ?? "");
|
|
6316
|
+
const links = [
|
|
6317
|
+
href ? `<a href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">Check on suveren.ai \u2197</a>` : "",
|
|
6318
|
+
interactive ? `<a href="${escapeHtml(detailLink(el.id))}" target="_top">Details</a>` : ""
|
|
6319
|
+
].filter(Boolean);
|
|
6320
|
+
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>`;
|
|
6321
|
+
}
|
|
6322
|
+
function renderApproval(el, interactive) {
|
|
6323
|
+
if (el.status === "unverifiable" || !el.data) return unverifiableCard(el);
|
|
6324
|
+
const d = el.data;
|
|
6325
|
+
const who = typeof d.whoLabel === "string" ? d.whoLabel : Array.isArray(d.who) ? d.who.join(", ") : "unknown";
|
|
6326
|
+
const asked = typeof d.createdAtLabel === "string" ? d.createdAtLabel : "unknown";
|
|
6327
|
+
const approved = typeof d.decidedAtLabel === "string" ? d.decidedAtLabel : "unknown";
|
|
6328
|
+
const waited = typeof d.waitLabel === "string" ? d.waitLabel : "unknown";
|
|
6329
|
+
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>` + (interactive ? `<div class="sv-el-row"><a href="${escapeHtml(detailLink(el.id, String(d.ticketId ?? "")))}" target="_top">Details</a></div>` : "") + `</div>`;
|
|
6330
|
+
}
|
|
6331
|
+
function renderMandate(el, interactive) {
|
|
6332
|
+
if (el.status === "unverifiable" || !el.data) return unverifiableCard(el);
|
|
6333
|
+
const d = el.data;
|
|
6334
|
+
const profileLabel = typeof d.profileLabel === "string" ? d.profileLabel : String(d.profile ?? "");
|
|
6335
|
+
const owners = Array.isArray(d.owners) && d.owners.length > 0 ? d.owners.join(", ") : "unknown owner";
|
|
6336
|
+
const limits = Array.isArray(d.limits) && d.limits.length > 0 ? d.limits.join(" \xB7 ") : "";
|
|
6337
|
+
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>` : "") + (interactive ? `<div class="sv-el-row"><a href="${escapeHtml(detailLink(el.id))}" target="_top">Details</a></div>` : "") + `</div>`;
|
|
6338
|
+
}
|
|
6339
|
+
var RECORD_FIELD_ORDER = [
|
|
6340
|
+
"subject",
|
|
6341
|
+
"from_email",
|
|
6342
|
+
"from_name",
|
|
6343
|
+
"to_json",
|
|
6344
|
+
"received_at",
|
|
6345
|
+
"number",
|
|
6346
|
+
"status",
|
|
6347
|
+
"net_total",
|
|
6348
|
+
"customer_id",
|
|
6349
|
+
"causingReceiptId",
|
|
6350
|
+
"message"
|
|
6351
|
+
];
|
|
6352
|
+
var RECORD_TIME_FIELDS = /* @__PURE__ */ new Set(["received_at", "created_at", "sent_at"]);
|
|
6353
|
+
function renderRecordField(key, d) {
|
|
6354
|
+
if (key === "net_total") {
|
|
6355
|
+
const currency = typeof d.currency === "string" ? d.currency : void 0;
|
|
6356
|
+
return `<div class="sv-el-row"><span>Value</span><span>${escapeHtml(formatCurrency(d.net_total, currency))}</span></div>`;
|
|
6357
|
+
}
|
|
6358
|
+
if (RECORD_TIME_FIELDS.has(key)) {
|
|
6359
|
+
const t = parseTimestampSeconds(d[key]);
|
|
6360
|
+
const display = t !== void 0 ? formatDateTime(t) : d[key];
|
|
6361
|
+
return `<div class="sv-el-row"><span>${escapeHtml(key)}</span><span>${escapeHtml(display)}</span></div>`;
|
|
6362
|
+
}
|
|
6363
|
+
return `<div class="sv-el-row"><span>${escapeHtml(key)}</span><span>${escapeHtml(d[key])}</span></div>`;
|
|
6364
|
+
}
|
|
6365
|
+
function renderRecord(el) {
|
|
6366
|
+
if (el.status === "unverifiable" || !el.data) return unverifiableCard(el);
|
|
6367
|
+
const d = el.data;
|
|
6368
|
+
const kind = String(d.kind ?? "record");
|
|
6369
|
+
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("");
|
|
6370
|
+
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>`;
|
|
6371
|
+
}
|
|
6372
|
+
function renderCaseTicketStep(caseElementId, node, isGoal, interactive) {
|
|
6373
|
+
const cls = isGoal ? "sv-step sv-step-goal" : "sv-step";
|
|
6374
|
+
const kind = isGoal ? "goal" : "ticket";
|
|
6375
|
+
const inner = `<div class="sv-step-k">${kind}</div><div class="sv-step-t">${escapeHtml(formatActionLabel(node.action))}</div>${formatDateTime(node.time)}`;
|
|
6376
|
+
if (!interactive) return `<div class="${cls}">${inner}</div>`;
|
|
6377
|
+
return `<a class="${cls}" href="${escapeHtml(detailLink(caseElementId, node.ticketId))}" target="_top">${inner}</a>`;
|
|
6378
|
+
}
|
|
6379
|
+
function renderCaseApprovalStep(approval) {
|
|
6380
|
+
const who = approval.whoLabel ?? (Array.isArray(approval.who) ? approval.who.join(", ") : "unknown");
|
|
6381
|
+
const asked = approval.createdAtLabel ?? "unknown";
|
|
6382
|
+
const decided = approval.decidedAtLabel ?? "unknown";
|
|
6383
|
+
const waited = approval.waitLabel ?? "unknown";
|
|
6384
|
+
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>`;
|
|
6385
|
+
}
|
|
6386
|
+
function renderCase(el, interactive) {
|
|
6387
|
+
if (el.status === "unverifiable" || !el.data) return unverifiableCard(el);
|
|
6388
|
+
const d = el.data;
|
|
6389
|
+
const steps = Array.isArray(d.steps) ? d.steps : [];
|
|
6390
|
+
const goal = d.goal;
|
|
6391
|
+
const approvalsByTicket = /* @__PURE__ */ new Map();
|
|
6392
|
+
if (Array.isArray(d.approvals)) {
|
|
6393
|
+
for (const a of d.approvals) {
|
|
6394
|
+
approvalsByTicket.set(a.ticketId, a);
|
|
6395
|
+
}
|
|
6396
|
+
}
|
|
6397
|
+
const start = d.start;
|
|
6398
|
+
const startStep = start ? { time: start.time, html: `<div class="sv-step"><div class="sv-step-k">start</div><div class="sv-step-t">Email in</div>${formatDateTime(start.time)}</div>` } : void 0;
|
|
6399
|
+
const ticketNodes = [
|
|
6400
|
+
...steps.map((s) => ({ node: s, isGoal: false })),
|
|
6401
|
+
...goal ? [{ node: goal, isGoal: true }] : []
|
|
6402
|
+
];
|
|
6403
|
+
const items = [];
|
|
6404
|
+
if (startStep) items.push(startStep);
|
|
6405
|
+
for (const { node, isGoal } of ticketNodes) {
|
|
6406
|
+
const approval = approvalsByTicket.get(node.ticketId);
|
|
6407
|
+
if (approval) {
|
|
6408
|
+
const approvalTime = Math.min(approval.decidedAt ?? approval.createdAt ?? node.time, node.time);
|
|
6409
|
+
items.push({ time: approvalTime, html: renderCaseApprovalStep(approval) });
|
|
6410
|
+
}
|
|
6411
|
+
items.push({ time: node.time, html: renderCaseTicketStep(el.id, node, isGoal, interactive) });
|
|
6412
|
+
}
|
|
6413
|
+
items.sort((a, b) => a.time - b.time);
|
|
6414
|
+
const stepHtml = items.map((i) => i.html).join("");
|
|
6415
|
+
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 ${formatDuration(d.totalDurationSeconds)}</b>${badge(el.status, "assembled by gateway")}</div>` + (el.status === "warning" && el.reason ? `<div class="sv-el-warn">${escapeHtml(el.reason)}</div>` : "") + `<div class="sv-tl">${stepHtml}</div>` + (interactive ? `<div class="sv-el-row"><a href="${escapeHtml(detailLink(el.id))}" target="_top">Case details</a></div>` : "") + `</div>`;
|
|
6416
|
+
}
|
|
6417
|
+
function renderMetric(el) {
|
|
6418
|
+
if (el.status === "unverifiable" || !el.data) return unverifiableCard(el);
|
|
6419
|
+
const d = el.data;
|
|
6420
|
+
const kind = String(d.kind ?? "");
|
|
6421
|
+
const label = METRIC_LABELS[kind] ?? kind;
|
|
6422
|
+
const display = formatMetricValue(kind, d.value);
|
|
6423
|
+
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>`;
|
|
6424
|
+
}
|
|
6425
|
+
function renderElement(el, interactive) {
|
|
6426
|
+
switch (el.kind) {
|
|
6427
|
+
case "sv-ticket":
|
|
6428
|
+
return renderTicket(el, interactive);
|
|
6429
|
+
case "sv-approval":
|
|
6430
|
+
return renderApproval(el, interactive);
|
|
6431
|
+
case "sv-mandate":
|
|
6432
|
+
return renderMandate(el, interactive);
|
|
6433
|
+
case "sv-record":
|
|
6434
|
+
return renderRecord(el);
|
|
6435
|
+
case "sv-case":
|
|
6436
|
+
return renderCase(el, interactive);
|
|
6437
|
+
case "sv-metric":
|
|
6438
|
+
return renderMetric(el);
|
|
6439
|
+
default:
|
|
6440
|
+
return unverifiableCard(el);
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
var DRAWN_ELEMENT_STYLES = `
|
|
6444
|
+
.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; }
|
|
6445
|
+
.sv-el-unverifiable { outline:2px solid #b91c1c; outline-offset:2px; }
|
|
6446
|
+
.sv-el-verified, .sv-el-warning { outline:2px solid #15803d; outline-offset:2px; }
|
|
6447
|
+
.sv-el-row { display:flex; justify-content:space-between; gap:10px; flex-wrap:wrap; margin:2px 0; }
|
|
6448
|
+
.sv-el-reason, .sv-el-warn { color:#b45309; font-size:12px; margin-top:4px; }
|
|
6449
|
+
.sv-el-intent { color:#666; font-size:12px; margin-top:4px; }
|
|
6450
|
+
.sv-badge { display:inline-flex; gap:4px; font-size:10.5px; font-weight:600; padding:1px 7px; border-radius:999px; white-space:nowrap; }
|
|
6451
|
+
.sv-badge-ok { color:#15803d; background:#ecfdf3; }
|
|
6452
|
+
.sv-badge-warn { color:#b45309; background:#fff7e6; }
|
|
6453
|
+
.sv-badge-bad { color:#b91c1c; background:#fdecea; }
|
|
6454
|
+
.sv-big { font-size:24px; font-weight:700; }
|
|
6455
|
+
.sv-tl { display:flex; flex-wrap:wrap; gap:10px 16px; padding:6px 0; }
|
|
6456
|
+
.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; }
|
|
6457
|
+
.sv-step-k { font-size:9.5px; text-transform:uppercase; color:#666; }
|
|
6458
|
+
.sv-step-t { font-weight:600; font-size:12px; }
|
|
6459
|
+
.sv-step-sub { color:#666; white-space:normal; }
|
|
6460
|
+
/* The approval step's text ("asked ... approved ... by ... (duration)") is
|
|
6461
|
+
* longer than a ticket step's \u2014 without a width to wrap against, a
|
|
6462
|
+
* flex item with flex-shrink:0 takes its one-line content width instead of
|
|
6463
|
+
* wrapping, which reads as the text being cut off at the container's visible
|
|
6464
|
+
* edge (polish 2026-10-05, second pass). */
|
|
6465
|
+
.sv-step-approval { max-width:240px; white-space:normal; }
|
|
6466
|
+
@media (max-width: 480px) {
|
|
6467
|
+
.sv-tl { flex-direction:column; overflow-x:visible; }
|
|
6468
|
+
.sv-step { min-width:0; }
|
|
6469
|
+
.sv-step-approval { max-width:none; }
|
|
6470
|
+
.sv-el-row { flex-direction:column; gap:2px; }
|
|
6471
|
+
}
|
|
6472
|
+
`;
|
|
6473
|
+
function renderReportHtml(html, elements, interactive = true) {
|
|
6474
|
+
const byId = new Map(elements.map((e) => [e.id, e]));
|
|
6475
|
+
const spans = findElementSpans(html);
|
|
6476
|
+
let out = "";
|
|
6477
|
+
let cursor = 0;
|
|
6478
|
+
for (const span of spans) {
|
|
6479
|
+
out += html.slice(cursor, span.start);
|
|
6480
|
+
const el = byId.get(span.id) ?? { id: span.id, kind: span.kind, attrs: {}, status: "unverifiable", reason: "No verification result for this element." };
|
|
6481
|
+
out += renderElement(el, interactive);
|
|
6482
|
+
cursor = span.end;
|
|
6483
|
+
}
|
|
6484
|
+
out += html.slice(cursor);
|
|
6485
|
+
const styleTag = `<style data-sv-drawn-styles="1">${DRAWN_ELEMENT_STYLES}</style>`;
|
|
6486
|
+
if (/<body[^>]*>/i.test(out)) {
|
|
6487
|
+
return out.replace(/<body([^>]*)>/i, (m) => `${m}${styleTag}`);
|
|
6488
|
+
}
|
|
6489
|
+
return styleTag + out;
|
|
6490
|
+
}
|
|
6491
|
+
|
|
6492
|
+
// src/lib/report/ticket-details.ts
|
|
6493
|
+
async function buildTicketDetails(archive, ticketIds) {
|
|
6494
|
+
const out = {};
|
|
6495
|
+
for (const id of ticketIds) {
|
|
6496
|
+
const [ticket, approval, mandate] = await Promise.all([
|
|
6497
|
+
resolveTicketElement(archive, id),
|
|
6498
|
+
resolveApprovalElement(archive, id),
|
|
6499
|
+
resolveMandateElement(archive, id)
|
|
6500
|
+
]);
|
|
6501
|
+
out[id] = { ticket, approval, mandate };
|
|
6502
|
+
}
|
|
6503
|
+
return out;
|
|
6504
|
+
}
|
|
6505
|
+
|
|
6506
|
+
// src/lib/report/export-report.ts
|
|
6507
|
+
function receiptId(receipt) {
|
|
6508
|
+
return typeof receipt.id === "string" ? receipt.id : "";
|
|
6509
|
+
}
|
|
6510
|
+
function buildExportBundle(params) {
|
|
6511
|
+
const { stored, archive, authorityServer, gatewayVersion, now = Math.floor(Date.now() / 1e3) } = params;
|
|
6512
|
+
const proof = stored.result.proof;
|
|
6513
|
+
const coverage = stored.result.coverage;
|
|
6514
|
+
const ticketIdSet = /* @__PURE__ */ new Set([...proof.ticketsReferenced, ...coverage.ticketsInPeriod]);
|
|
6515
|
+
const entries = archive.getReceipts().filter((r) => ticketIdSet.has(receiptId(r.receipt)));
|
|
6516
|
+
const tickets = entries.map((r) => r.receipt);
|
|
6517
|
+
const authorizationIds = new Set(entries.map((r) => r.authorizationId));
|
|
6518
|
+
const authorizations = {};
|
|
6519
|
+
for (const a of archive.getAuthorizations()) {
|
|
6520
|
+
if (authorizationIds.has(a.authorizationId)) authorizations[a.authorizationId] = a;
|
|
6521
|
+
}
|
|
6522
|
+
return {
|
|
6523
|
+
format: "suveren-report-export",
|
|
6524
|
+
version: 1,
|
|
6525
|
+
exportedAt: now,
|
|
6526
|
+
gatewayVersion,
|
|
6527
|
+
report: { html: stored.html, savedAt: stored.savedAt, checkedAt: stored.checkedAt },
|
|
6528
|
+
proof,
|
|
6529
|
+
coverage,
|
|
6530
|
+
authorityServer,
|
|
6531
|
+
tickets,
|
|
6532
|
+
authorizations
|
|
6533
|
+
};
|
|
6534
|
+
}
|
|
6535
|
+
function countLine(count, noneText = "none in this report") {
|
|
6536
|
+
return count > 0 ? `${count} \u2713` : noneText;
|
|
6537
|
+
}
|
|
6538
|
+
function renderProofCoveragePanels(proof, coverage) {
|
|
6539
|
+
const casesLine = coverage.emailExportError ? "unknown \u2014 the email simulator could not be read" : `${coverage.coveredCases.length} of ${coverage.loadedCases.length}`;
|
|
6540
|
+
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>` : "";
|
|
6541
|
+
const missingParts = [];
|
|
6542
|
+
if (coverage.missingCases.length > 0) missingParts.push(`cases ${coverage.missingCases.map(escapeHtml).join(", ")}`);
|
|
6543
|
+
if (coverage.ticketsNotReferenced.length > 0) missingParts.push(`${coverage.ticketsNotReferenced.length} ticket${coverage.ticketsNotReferenced.length === 1 ? "" : "s"}`);
|
|
6544
|
+
const missingLine = missingParts.length > 0 ? `<p class="sv-export-missing">Not in the report: ${missingParts.join(" \xB7 ")}</p>` : "";
|
|
6545
|
+
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>` : "";
|
|
6546
|
+
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>`;
|
|
6547
|
+
}
|
|
6548
|
+
function ensureHtmlDocument(html) {
|
|
6549
|
+
if (/<html[^>]*>/i.test(html)) return html;
|
|
6550
|
+
return `<!doctype html><html><head><meta charset="utf-8"></head><body>${html}</body></html>`;
|
|
6551
|
+
}
|
|
6552
|
+
function injectIntoHead(html, insert) {
|
|
6553
|
+
if (/<head[^>]*>/i.test(html)) return html.replace(/<head([^>]*)>/i, (m) => `${m}${insert}`);
|
|
6554
|
+
if (/<html[^>]*>/i.test(html)) return html.replace(/<html([^>]*)>/i, (m) => `${m}<head>${insert}</head>`);
|
|
6555
|
+
return `<head>${insert}</head>${html}`;
|
|
6556
|
+
}
|
|
6557
|
+
function injectAfterBodyOpen(html, insert) {
|
|
6558
|
+
if (/<body[^>]*>/i.test(html)) return html.replace(/<body([^>]*)>/i, (m) => `${m}${insert}`);
|
|
6559
|
+
return `${insert}${html}`;
|
|
6560
|
+
}
|
|
6561
|
+
function injectBeforeBodyClose(html, insert) {
|
|
6562
|
+
if (/<\/body>/i.test(html)) return html.replace(/<\/body>/i, `${insert}</body>`);
|
|
6563
|
+
return `${html}${insert}`;
|
|
6564
|
+
}
|
|
6565
|
+
var EXPORT_PAGE_STYLES = `
|
|
6566
|
+
.sv-export-header { font:13px/1.5 system-ui, sans-serif; background:#f6f6f4; border-bottom:1px solid #e5e5e5; padding:14px 20px; }
|
|
6567
|
+
.sv-export-meta { margin:0 0 6px 0; font-weight:600; }
|
|
6568
|
+
.sv-export-howto { margin:0; color:#444; }
|
|
6569
|
+
.sv-export-howto code { background:#eee; border-radius:4px; padding:1px 5px; font-size:12px; }
|
|
6570
|
+
.sv-export-layout { display:flex; gap:20px; align-items:flex-start; padding:20px; font:13px/1.5 system-ui, sans-serif; }
|
|
6571
|
+
.sv-export-main { flex:1 1 auto; min-width:0; }
|
|
6572
|
+
.sv-export-side { flex:0 0 260px; }
|
|
6573
|
+
.sv-export-card { border:1px solid #e5e5e5; border-radius:10px; padding:10px 12px; margin-bottom:12px; background:#fff; }
|
|
6574
|
+
.sv-export-card-title { font-weight:700; margin-bottom:6px; }
|
|
6575
|
+
.sv-export-row { display:flex; justify-content:space-between; gap:10px; margin:2px 0; }
|
|
6576
|
+
.sv-export-note, .sv-export-missing { color:#b45309; font-size:12px; margin:4px 0 0 0; }
|
|
6577
|
+
@media (max-width: 720px) {
|
|
6578
|
+
.sv-export-layout { flex-direction:column; }
|
|
6579
|
+
.sv-export-side { flex:1 1 auto; width:100%; }
|
|
6580
|
+
}
|
|
6581
|
+
`;
|
|
6582
|
+
function formatDateTimeWithYear(value) {
|
|
6583
|
+
const base = formatDateTime(value);
|
|
6584
|
+
const year = new Date(value * 1e3).getFullYear();
|
|
6585
|
+
return base.replace(",", ` ${year},`);
|
|
6586
|
+
}
|
|
6587
|
+
function utcOffsetLabel(value) {
|
|
6588
|
+
const offsetMin = -new Date(value * 1e3).getTimezoneOffset();
|
|
6589
|
+
const sign = offsetMin >= 0 ? "+" : "-";
|
|
6590
|
+
const abs = Math.abs(offsetMin);
|
|
6591
|
+
const hh = Math.floor(abs / 60);
|
|
6592
|
+
const mm = abs % 60;
|
|
6593
|
+
return `UTC${sign}${hh}${mm ? ":" + String(mm).padStart(2, "0") : ""}`;
|
|
6594
|
+
}
|
|
6595
|
+
function buildExportDocument(params) {
|
|
6596
|
+
const { bundle, renderedHtml } = params;
|
|
6597
|
+
const exportedDate = new Date(bundle.exportedAt * 1e3).toISOString().slice(0, 10);
|
|
6598
|
+
const exportedLabel = formatDateTimeWithYear(bundle.exportedAt);
|
|
6599
|
+
const checkedLabel = formatDateTime(bundle.report.checkedAt);
|
|
6600
|
+
const tzLabel = utcOffsetLabel(bundle.exportedAt);
|
|
6601
|
+
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></div>`;
|
|
6602
|
+
const proofScriptJson = JSON.stringify(bundle).replace(/<\//g, "<\\/");
|
|
6603
|
+
const proofScript = `<script type="application/json" id="suveren-proof">${proofScriptJson}</script>`;
|
|
6604
|
+
let doc = ensureHtmlDocument(renderedHtml);
|
|
6605
|
+
doc = injectIntoHead(doc, `<meta charset="utf-8"><title>Suveren report export \u2014 ${escapeHtml(exportedDate)}</title><style>${DRAWN_ELEMENT_STYLES}${EXPORT_PAGE_STYLES}</style>`);
|
|
6606
|
+
doc = injectAfterBodyOpen(doc, headerHtml + `<div class="sv-export-layout"><div class="sv-export-main">`);
|
|
6607
|
+
doc = injectBeforeBodyClose(doc, `</div>${renderProofCoveragePanels(bundle.proof, bundle.coverage)}</div>${proofScript}`);
|
|
6608
|
+
return doc;
|
|
6609
|
+
}
|
|
6610
|
+
function suggestedFilename(bundle) {
|
|
6611
|
+
const date = new Date(bundle.exportedAt * 1e3).toISOString().slice(0, 10);
|
|
6612
|
+
return `suveren-report-${date}.html`;
|
|
6613
|
+
}
|
|
6614
|
+
|
|
5564
6615
|
// src/lib/profile-loader.ts
|
|
5565
6616
|
import { readFileSync as readFileSync16, existsSync as existsSync15 } from "fs";
|
|
5566
6617
|
import { join as join15, resolve as resolve2 } from "path";
|
|
@@ -5620,6 +6671,251 @@ function builtinText(text) {
|
|
|
5620
6671
|
return { content: [{ type: "text", text }] };
|
|
5621
6672
|
}
|
|
5622
6673
|
|
|
6674
|
+
// src/lib/builtins/report.ts
|
|
6675
|
+
var MAX_REPORT_HTML_BYTES = 512 * 1024;
|
|
6676
|
+
var READ_GATE = { category: "read", boundField: "read_access", requiredValue: "unlimited" };
|
|
6677
|
+
var EMAIL_RECORD_KINDS = ["inbox", "sent", "changes", "refusals"];
|
|
6678
|
+
var ERP_RECORD_KINDS = ["quotes", "orders", "changes", "refusals"];
|
|
6679
|
+
var CRM_RECORD_KINDS = ["contacts", "deals", "tasks", "activities", "changes", "refusals"];
|
|
6680
|
+
function errorText(text) {
|
|
6681
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
6682
|
+
}
|
|
6683
|
+
function listTicketsTool(deps) {
|
|
6684
|
+
return {
|
|
6685
|
+
name: "list_tickets",
|
|
6686
|
+
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.",
|
|
6687
|
+
inputSchema: {
|
|
6688
|
+
type: "object",
|
|
6689
|
+
properties: {
|
|
6690
|
+
since: { type: "number", description: "Only tickets timestamped at or after this unix-seconds value." },
|
|
6691
|
+
until: { type: "number", description: "Only tickets timestamped at or before this unix-seconds value." }
|
|
6692
|
+
}
|
|
6693
|
+
},
|
|
6694
|
+
handler: async (args) => {
|
|
6695
|
+
const since = typeof args.since === "number" ? args.since : void 0;
|
|
6696
|
+
const until = typeof args.until === "number" ? args.until : void 0;
|
|
6697
|
+
const tickets = deps.reportSources.archive.getReceipts().map((entry) => {
|
|
6698
|
+
const r = entry.receipt;
|
|
6699
|
+
const time = typeof r.timestamp === "number" ? r.timestamp : void 0;
|
|
6700
|
+
return {
|
|
6701
|
+
id: typeof r.id === "string" ? r.id : String(r.id ?? ""),
|
|
6702
|
+
time,
|
|
6703
|
+
action: r.action,
|
|
6704
|
+
actionType: r.actionType ?? null,
|
|
6705
|
+
profile: r.profileId,
|
|
6706
|
+
authorizationId: r.authorizationId,
|
|
6707
|
+
limitsUsed: r.limits ?? r.executionContext ?? {},
|
|
6708
|
+
hasApproval: Boolean(entry.proposal)
|
|
6709
|
+
};
|
|
6710
|
+
}).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));
|
|
6711
|
+
return builtinText(JSON.stringify({ tickets }, null, 2));
|
|
6712
|
+
}
|
|
6713
|
+
};
|
|
6714
|
+
}
|
|
6715
|
+
function getTicketTool(deps) {
|
|
6716
|
+
return {
|
|
6717
|
+
name: "get_ticket",
|
|
6718
|
+
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.',
|
|
6719
|
+
inputSchema: {
|
|
6720
|
+
type: "object",
|
|
6721
|
+
properties: { id: { type: "string", description: "The ticket id, as listed by list_tickets." } },
|
|
6722
|
+
required: ["id"]
|
|
6723
|
+
},
|
|
6724
|
+
handler: async (args) => {
|
|
6725
|
+
const id = typeof args.id === "string" ? args.id.trim() : "";
|
|
6726
|
+
if (!id) return errorText("id is required.");
|
|
6727
|
+
const details = await buildTicketDetails(deps.reportSources.archive, [id]);
|
|
6728
|
+
const detail = details[id];
|
|
6729
|
+
if (!detail || detail.ticket.status !== "verified") {
|
|
6730
|
+
return errorText(detail?.ticket.reason ?? `No ticket "${id}" in the local archive.`);
|
|
6731
|
+
}
|
|
6732
|
+
return builtinText(JSON.stringify({
|
|
6733
|
+
ticket: detail.ticket.data,
|
|
6734
|
+
approval: detail.approval.status === "verified" ? { verified: true, ...detail.approval.data } : { verified: false, reason: detail.approval.reason },
|
|
6735
|
+
mandate: detail.mandate.status === "verified" ? { verified: true, ...detail.mandate.data } : { verified: false, reason: detail.mandate.reason }
|
|
6736
|
+
}, null, 2));
|
|
6737
|
+
}
|
|
6738
|
+
};
|
|
6739
|
+
}
|
|
6740
|
+
function listCasesTool(deps) {
|
|
6741
|
+
return {
|
|
6742
|
+
name: "list_cases",
|
|
6743
|
+
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.",
|
|
6744
|
+
inputSchema: { type: "object", properties: {} },
|
|
6745
|
+
handler: async () => {
|
|
6746
|
+
let exported;
|
|
6747
|
+
try {
|
|
6748
|
+
exported = await deps.reportSources.runExport("email");
|
|
6749
|
+
} catch (err) {
|
|
6750
|
+
return errorText(`Could not read email records: ${err instanceof Error ? err.message : String(err)}`);
|
|
6751
|
+
}
|
|
6752
|
+
if (!isEmailExport(exported)) return errorText("Email records were returned in an unexpected shape.");
|
|
6753
|
+
const exp = exported;
|
|
6754
|
+
const cases = exp.inbox.filter((m) => !!m.case_id).map((m) => ({
|
|
6755
|
+
caseId: m.case_id,
|
|
6756
|
+
startMessageId: m.id,
|
|
6757
|
+
from: m.from_email,
|
|
6758
|
+
subject: m.subject,
|
|
6759
|
+
receivedAt: m.received_at
|
|
6760
|
+
}));
|
|
6761
|
+
const sent = exp.sent.map((m) => ({
|
|
6762
|
+
id: m.id,
|
|
6763
|
+
inReplyTo: m.in_reply_to ?? null,
|
|
6764
|
+
receiptId: m.receipt_id ?? null,
|
|
6765
|
+
subject: m.subject,
|
|
6766
|
+
sentAt: m.received_at
|
|
6767
|
+
}));
|
|
6768
|
+
return builtinText(JSON.stringify({
|
|
6769
|
+
testDataLoadedAt: exp.simulation_load?.loaded_at ?? null,
|
|
6770
|
+
cases,
|
|
6771
|
+
sent
|
|
6772
|
+
}, null, 2));
|
|
6773
|
+
}
|
|
6774
|
+
};
|
|
6775
|
+
}
|
|
6776
|
+
function recordKindsFor(system) {
|
|
6777
|
+
if (system === "email") return EMAIL_RECORD_KINDS;
|
|
6778
|
+
if (system === "erp") return ERP_RECORD_KINDS;
|
|
6779
|
+
return CRM_RECORD_KINDS;
|
|
6780
|
+
}
|
|
6781
|
+
function getRecordsTool(deps) {
|
|
6782
|
+
return {
|
|
6783
|
+
name: "get_records",
|
|
6784
|
+
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.`,
|
|
6785
|
+
inputSchema: {
|
|
6786
|
+
type: "object",
|
|
6787
|
+
properties: {
|
|
6788
|
+
system: { type: "string", enum: ["email", "erp", "crm"] },
|
|
6789
|
+
kind: { type: "string", description: "Optional: one table name within that system's records." }
|
|
6790
|
+
},
|
|
6791
|
+
required: ["system"]
|
|
6792
|
+
},
|
|
6793
|
+
handler: async (args) => {
|
|
6794
|
+
const system = args.system;
|
|
6795
|
+
if (system !== "email" && system !== "erp" && system !== "crm") {
|
|
6796
|
+
return errorText('system must be one of "email", "erp", "crm".');
|
|
6797
|
+
}
|
|
6798
|
+
let exported;
|
|
6799
|
+
try {
|
|
6800
|
+
exported = await deps.reportSources.runExport(system);
|
|
6801
|
+
} catch (err) {
|
|
6802
|
+
return errorText(`Could not read ${system} records: ${err instanceof Error ? err.message : String(err)}`);
|
|
6803
|
+
}
|
|
6804
|
+
const ok = system === "email" ? isEmailExport(exported) : system === "erp" ? isErpExport(exported) : isCrmExport(exported);
|
|
6805
|
+
if (!ok) return errorText(`${system} records were returned in an unexpected shape.`);
|
|
6806
|
+
const allowedKinds = recordKindsFor(system);
|
|
6807
|
+
const requestedKind = typeof args.kind === "string" ? args.kind : void 0;
|
|
6808
|
+
if (requestedKind && !allowedKinds.includes(requestedKind)) {
|
|
6809
|
+
return errorText(`kind must be one of ${allowedKinds.join(", ")} for system "${system}".`);
|
|
6810
|
+
}
|
|
6811
|
+
const kinds = requestedKind ? [requestedKind] : allowedKinds;
|
|
6812
|
+
const records = {};
|
|
6813
|
+
const exp = exported;
|
|
6814
|
+
for (const kind of kinds) {
|
|
6815
|
+
records[kind] = exp[kind] ?? [];
|
|
6816
|
+
}
|
|
6817
|
+
return builtinText(JSON.stringify({ system, records }, null, 2));
|
|
6818
|
+
}
|
|
6819
|
+
};
|
|
6820
|
+
}
|
|
6821
|
+
function summarizeWrite(result) {
|
|
6822
|
+
const verified = result.elements.filter((e) => e.status === "verified");
|
|
6823
|
+
const warnings = result.elements.filter((e) => e.status === "warning");
|
|
6824
|
+
const unverifiable = result.elements.filter((e) => e.status === "unverifiable");
|
|
6825
|
+
const lines = [];
|
|
6826
|
+
lines.push(
|
|
6827
|
+
`Report stored. ${verified.length} element(s) verified, ${warnings.length} warning(s), ${unverifiable.length} not verifiable.`
|
|
6828
|
+
);
|
|
6829
|
+
if (warnings.length > 0) {
|
|
6830
|
+
lines.push("Warnings:");
|
|
6831
|
+
for (const w of warnings) lines.push(` - ${w.kind} (${w.id}): ${w.reason ?? "unspecified"}`);
|
|
6832
|
+
}
|
|
6833
|
+
if (unverifiable.length > 0) {
|
|
6834
|
+
lines.push("Not verifiable \u2014 fix these references and write again:");
|
|
6835
|
+
for (const u of unverifiable) lines.push(` - ${u.kind} (${u.id}): ${u.reason ?? "unspecified"}`);
|
|
6836
|
+
}
|
|
6837
|
+
const cov = result.coverage;
|
|
6838
|
+
if (cov.emailExportError) {
|
|
6839
|
+
lines.push(`Coverage could not be checked: ${cov.emailExportError}`);
|
|
6840
|
+
} else {
|
|
6841
|
+
lines.push(`Coverage: ${cov.coveredCases.length} of ${cov.loadedCases.length} loaded case(s) covered.`);
|
|
6842
|
+
if (cov.missingCases.length > 0) lines.push(`Missing cases (not defined with sv-case): ${cov.missingCases.join(", ")}`);
|
|
6843
|
+
if (cov.ticketsNotReferenced.length > 0) {
|
|
6844
|
+
lines.push(`Tickets from the test period not referenced anywhere in the report: ${cov.ticketsNotReferenced.join(", ")}`);
|
|
6845
|
+
}
|
|
6846
|
+
}
|
|
6847
|
+
return lines.join("\n");
|
|
6848
|
+
}
|
|
6849
|
+
function writeReportRefusal(args) {
|
|
6850
|
+
const html = typeof args.html === "string" ? args.html : "";
|
|
6851
|
+
if (!html.trim()) return "`html` must be a non-empty string.";
|
|
6852
|
+
const bytes = Buffer.byteLength(html, "utf8");
|
|
6853
|
+
if (bytes > MAX_REPORT_HTML_BYTES) {
|
|
6854
|
+
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.`;
|
|
6855
|
+
}
|
|
6856
|
+
return void 0;
|
|
6857
|
+
}
|
|
6858
|
+
function writeReportTool(deps) {
|
|
6859
|
+
return {
|
|
6860
|
+
name: "write_report",
|
|
6861
|
+
// The brief IS the description — this is how it reaches the AI (R3/R4 plan,
|
|
6862
|
+
// the same pattern mail.json's load_simulation uses for its package guide).
|
|
6863
|
+
description: REPORT_BRIEF,
|
|
6864
|
+
inputSchema: {
|
|
6865
|
+
type: "object",
|
|
6866
|
+
properties: {
|
|
6867
|
+
html: { type: "string", description: "The full report HTML \u2014 see the tool description for how to write it." },
|
|
6868
|
+
receipt_id: { type: "string" }
|
|
6869
|
+
},
|
|
6870
|
+
required: ["html"]
|
|
6871
|
+
},
|
|
6872
|
+
// Empty/oversize html is refused here, BEFORE the gate requests a ticket
|
|
6873
|
+
// (precheckBuiltin) — a refused write must not consume report_daily_max.
|
|
6874
|
+
validate: writeReportRefusal,
|
|
6875
|
+
handler: async (args) => {
|
|
6876
|
+
const refusal = writeReportRefusal(args);
|
|
6877
|
+
if (refusal) return errorText(`Refused: ${refusal}`);
|
|
6878
|
+
const html = args.html;
|
|
6879
|
+
if (deps.state.reportStore.isLocked()) {
|
|
6880
|
+
return errorText("The report store is locked (the vault is not unlocked) \u2014 cannot save a report right now.");
|
|
6881
|
+
}
|
|
6882
|
+
try {
|
|
6883
|
+
const stored = await deps.state.reportStore.saveReport(html, deps.reportSources);
|
|
6884
|
+
return builtinText(summarizeWrite(stored.result));
|
|
6885
|
+
} catch (err) {
|
|
6886
|
+
return errorText(`Could not verify and store the report: ${err instanceof Error ? err.message : String(err)}`);
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6889
|
+
};
|
|
6890
|
+
}
|
|
6891
|
+
var reportBuiltin = (deps) => ({
|
|
6892
|
+
id: "report",
|
|
6893
|
+
name: "Report",
|
|
6894
|
+
// Short name — matches the real registration in profile-loader.ts (full id
|
|
6895
|
+
// AND short name both registered); profileMatches() accepts either.
|
|
6896
|
+
profile: "reporting",
|
|
6897
|
+
// Touches no real system (reads the local archive + each simulator's own
|
|
6898
|
+
// read-only export CLI; writes only the local, vault-encrypted report file)
|
|
6899
|
+
// — safe during a simulation-mode test.
|
|
6900
|
+
simulation: true,
|
|
6901
|
+
toolGating: {
|
|
6902
|
+
overrides: {
|
|
6903
|
+
list_tickets: READ_GATE,
|
|
6904
|
+
get_ticket: READ_GATE,
|
|
6905
|
+
list_cases: READ_GATE,
|
|
6906
|
+
get_records: READ_GATE,
|
|
6907
|
+
write_report: { executionMapping: {}, staticExecution: { action_type: "report" } }
|
|
6908
|
+
}
|
|
6909
|
+
},
|
|
6910
|
+
tools: [
|
|
6911
|
+
listTicketsTool(deps),
|
|
6912
|
+
getTicketTool(deps),
|
|
6913
|
+
listCasesTool(deps),
|
|
6914
|
+
getRecordsTool(deps),
|
|
6915
|
+
writeReportTool(deps)
|
|
6916
|
+
]
|
|
6917
|
+
});
|
|
6918
|
+
|
|
5623
6919
|
// src/lib/cp-mandate.ts
|
|
5624
6920
|
var CP_PORT2 = process.env.SUVEREN_CP_PORT ?? "3402";
|
|
5625
6921
|
var CP_BASE2 = process.env.SUVEREN_CP_INTERNAL_URL ?? `http://127.0.0.1:${CP_PORT2}`;
|
|
@@ -5841,7 +7137,7 @@ ${guide.body.trim()}`);
|
|
|
5841
7137
|
inputSchema: {
|
|
5842
7138
|
type: "object",
|
|
5843
7139
|
properties: {
|
|
5844
|
-
team: { type: "string", description:
|
|
7140
|
+
team: { type: "string", description: 'Team name or id, or "personal". Omit to use the workspace of your Delegation mandate.' },
|
|
5845
7141
|
profile: { type: "string", description: 'Profile, e.g. "sales" (newest version) or a full profile id.' },
|
|
5846
7142
|
limits: { type: "object", description: "The limits (bounds) by field name, as the profile defines them." },
|
|
5847
7143
|
scope: { type: "object", description: "The scope (context) by field name, as the profile defines it." },
|
|
@@ -5886,7 +7182,7 @@ function mandateRequest(args) {
|
|
|
5886
7182
|
}
|
|
5887
7183
|
|
|
5888
7184
|
// src/lib/builtins/index.ts
|
|
5889
|
-
var BUILTIN_FACTORIES = [setupBuiltin];
|
|
7185
|
+
var BUILTIN_FACTORIES = [setupBuiltin, reportBuiltin];
|
|
5890
7186
|
function registerBuiltins(deps, factories = BUILTIN_FACTORIES) {
|
|
5891
7187
|
const registered = [];
|
|
5892
7188
|
for (const factory of factories) {
|
|
@@ -5985,7 +7281,7 @@ function isAlive(pid) {
|
|
|
5985
7281
|
}
|
|
5986
7282
|
|
|
5987
7283
|
// bin/http.ts
|
|
5988
|
-
import { homedir as
|
|
7284
|
+
import { homedir as homedir11 } from "os";
|
|
5989
7285
|
import { join as join19 } from "path";
|
|
5990
7286
|
|
|
5991
7287
|
// src/lib/mcp-as-tracker.ts
|
|
@@ -6011,12 +7307,16 @@ function writeMcpPairedAsUrl(dataDir2, asUrl) {
|
|
|
6011
7307
|
}
|
|
6012
7308
|
|
|
6013
7309
|
// bin/http.ts
|
|
6014
|
-
var dataDir = process.env.SUVEREN_DATA_DIR ?? join19(
|
|
7310
|
+
var dataDir = process.env.SUVEREN_DATA_DIR ?? join19(homedir11(), ".suveren");
|
|
6015
7311
|
var spUrl = resolveAsUrl(dataDir);
|
|
6016
7312
|
validatePinTlsForUrl(resolvePinTls(dataDir), spUrl);
|
|
6017
7313
|
var port = parseInt(process.env.SUVEREN_MCP_PORT ?? "3430", 10);
|
|
6018
7314
|
setAsBaseUrl(spUrl);
|
|
6019
7315
|
var state = new SharedState(spUrl, void 0, dataDir);
|
|
7316
|
+
var reportSources = {
|
|
7317
|
+
archive: state.receiptArchive,
|
|
7318
|
+
runExport: createConnectorExportRunner({ integrationsBinDir: getIntegrationsBinDir(), dataDir })
|
|
7319
|
+
};
|
|
6020
7320
|
(function repairIfAsUrlChanged() {
|
|
6021
7321
|
const lastKnown = readMcpPairedAsUrl(dataDir);
|
|
6022
7322
|
if (lastKnown !== null && lastKnown !== spUrl) {
|
|
@@ -6092,7 +7392,8 @@ app.post("/internal/configure", internalOnly, (req, res) => {
|
|
|
6092
7392
|
state.gateStore.setVaultKey(key);
|
|
6093
7393
|
state.denialLog.setVaultKey(key);
|
|
6094
7394
|
state.receiptArchive.setVaultKey(key);
|
|
6095
|
-
|
|
7395
|
+
state.reportStore.setVaultKey(key);
|
|
7396
|
+
console.error("[Suveren MCP] Vault key configured \u2014 gate store + denial log + receipt archive + report store encryption active");
|
|
6096
7397
|
}
|
|
6097
7398
|
if (apiKey) {
|
|
6098
7399
|
state.spClient.setApiKey(apiKey);
|
|
@@ -6432,6 +7733,107 @@ app.get("/internal/evidence", internalOnly, (_req, res) => {
|
|
|
6432
7733
|
gates: state.gateStore.getAll()
|
|
6433
7734
|
});
|
|
6434
7735
|
});
|
|
7736
|
+
async function reportResponsePayload(stored) {
|
|
7737
|
+
const ticketDetails = await buildTicketDetails(state.receiptArchive, stored.result.proof.ticketsReferenced);
|
|
7738
|
+
return {
|
|
7739
|
+
savedAt: stored.savedAt,
|
|
7740
|
+
checkedAt: stored.checkedAt,
|
|
7741
|
+
renderedHtml: renderReportHtml(stored.result.html, stored.result.elements),
|
|
7742
|
+
proof: stored.result.proof,
|
|
7743
|
+
coverage: stored.result.coverage,
|
|
7744
|
+
elements: stored.result.elements,
|
|
7745
|
+
ticketDetails
|
|
7746
|
+
};
|
|
7747
|
+
}
|
|
7748
|
+
app.get("/internal/report", internalOnly, async (_req, res) => {
|
|
7749
|
+
if (state.reportStore.isLocked()) {
|
|
7750
|
+
res.status(503).json({ error: "Vault locked \u2014 the report is encrypted until sign-in." });
|
|
7751
|
+
return;
|
|
7752
|
+
}
|
|
7753
|
+
const stored = state.reportStore.getReport();
|
|
7754
|
+
if (!stored) {
|
|
7755
|
+
res.json({ report: null });
|
|
7756
|
+
return;
|
|
7757
|
+
}
|
|
7758
|
+
res.json({ report: await reportResponsePayload(stored) });
|
|
7759
|
+
});
|
|
7760
|
+
app.post("/internal/report", internalOnly, async (req, res) => {
|
|
7761
|
+
if (state.reportStore.isLocked()) {
|
|
7762
|
+
res.status(503).json({ error: "Vault locked \u2014 cannot save a report until sign-in." });
|
|
7763
|
+
return;
|
|
7764
|
+
}
|
|
7765
|
+
const html = req.body?.html;
|
|
7766
|
+
if (typeof html !== "string" || !html.trim()) {
|
|
7767
|
+
res.status(400).json({ error: "Missing required field: html (non-empty string)" });
|
|
7768
|
+
return;
|
|
7769
|
+
}
|
|
7770
|
+
try {
|
|
7771
|
+
const stored = await state.reportStore.saveReport(html, reportSources);
|
|
7772
|
+
res.json({ report: await reportResponsePayload(stored) });
|
|
7773
|
+
} catch (err) {
|
|
7774
|
+
console.error("[Suveren MCP] /internal/report save failed:", err);
|
|
7775
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "Report verification failed" });
|
|
7776
|
+
}
|
|
7777
|
+
});
|
|
7778
|
+
app.post("/internal/report/recheck", internalOnly, async (_req, res) => {
|
|
7779
|
+
if (state.reportStore.isLocked()) {
|
|
7780
|
+
res.status(503).json({ error: "Vault locked \u2014 cannot recheck until sign-in." });
|
|
7781
|
+
return;
|
|
7782
|
+
}
|
|
7783
|
+
try {
|
|
7784
|
+
const stored = await state.reportStore.recheck(reportSources);
|
|
7785
|
+
if (!stored) {
|
|
7786
|
+
res.json({ report: null });
|
|
7787
|
+
return;
|
|
7788
|
+
}
|
|
7789
|
+
res.json({ report: await reportResponsePayload(stored) });
|
|
7790
|
+
} catch (err) {
|
|
7791
|
+
console.error("[Suveren MCP] /internal/report/recheck failed:", err);
|
|
7792
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "Recheck failed" });
|
|
7793
|
+
}
|
|
7794
|
+
});
|
|
7795
|
+
function resolveExportAuthorityServer() {
|
|
7796
|
+
const pin = readPairing(dataDir);
|
|
7797
|
+
if (pin) return { url: pin.asUrl, publicKeyHex: pin.publicKeyHex };
|
|
7798
|
+
const withKey = state.receiptArchive.getReceipts().filter((r) => !!r.asPublicKey).sort((a, b) => b.archivedAt - a.archivedAt);
|
|
7799
|
+
if (withKey.length > 0) return { url: withKey[0].asUrl, publicKeyHex: withKey[0].asPublicKey };
|
|
7800
|
+
return { url: spUrl, publicKeyHex: "" };
|
|
7801
|
+
}
|
|
7802
|
+
app.get("/internal/report/export", internalOnly, async (req, res) => {
|
|
7803
|
+
if (state.reportStore.isLocked() || state.receiptArchive.isLocked()) {
|
|
7804
|
+
res.status(503).json({ error: "Vault locked \u2014 the report is encrypted until sign-in." });
|
|
7805
|
+
return;
|
|
7806
|
+
}
|
|
7807
|
+
let stored;
|
|
7808
|
+
try {
|
|
7809
|
+
stored = await state.reportStore.recheck(reportSources);
|
|
7810
|
+
} catch (err) {
|
|
7811
|
+
console.error("[Suveren MCP] /internal/report/export recheck failed:", err);
|
|
7812
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "Recheck failed" });
|
|
7813
|
+
return;
|
|
7814
|
+
}
|
|
7815
|
+
if (!stored) {
|
|
7816
|
+
res.status(404).json({ error: "No report to export yet \u2014 the AI writes it with the Reporting mandate." });
|
|
7817
|
+
return;
|
|
7818
|
+
}
|
|
7819
|
+
const authorityServer = resolveExportAuthorityServer();
|
|
7820
|
+
if (!authorityServer.publicKeyHex) {
|
|
7821
|
+
res.status(503).json({
|
|
7822
|
+
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."
|
|
7823
|
+
});
|
|
7824
|
+
return;
|
|
7825
|
+
}
|
|
7826
|
+
const gatewayVersion = typeof req.query.gatewayVersion === "string" ? req.query.gatewayVersion : "unknown";
|
|
7827
|
+
const bundle = buildExportBundle({
|
|
7828
|
+
stored,
|
|
7829
|
+
archive: state.receiptArchive,
|
|
7830
|
+
authorityServer,
|
|
7831
|
+
gatewayVersion
|
|
7832
|
+
});
|
|
7833
|
+
const renderedHtml = renderReportHtml(stored.result.html, stored.result.elements, false);
|
|
7834
|
+
const document = buildExportDocument({ bundle, renderedHtml });
|
|
7835
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8").setHeader("Content-Disposition", `attachment; filename="${suggestedFilename(bundle)}"`).setHeader("Cache-Control", "no-store").send(document);
|
|
7836
|
+
});
|
|
6435
7837
|
app.get("/internal/brief", internalOnly, (_req, res) => {
|
|
6436
7838
|
try {
|
|
6437
7839
|
const enriched = state.getEnrichedAuthorizations();
|
|
@@ -6502,7 +7904,7 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
6502
7904
|
console.error(`[Suveren MCP] SP server: ${spUrl}`);
|
|
6503
7905
|
profilesLoaded = loadProfiles();
|
|
6504
7906
|
loadManifests();
|
|
6505
|
-
registerBuiltins({ state, integrationManager });
|
|
7907
|
+
registerBuiltins({ state, integrationManager, reportSources });
|
|
6506
7908
|
if (process.env.SUVEREN_DISABLE_AUTO_INTEGRATIONS !== "1" && integrationRegistry.getEnabled().length === 0) {
|
|
6507
7909
|
const personalManifests = getAllManifests().filter((m) => m.personalDefault);
|
|
6508
7910
|
for (const manifest of personalManifests) {
|
|
@@ -6545,7 +7947,7 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
6545
7947
|
(proposal) => executeCommitted(proposal, state, integrationManager)
|
|
6546
7948
|
);
|
|
6547
7949
|
installCommittedExecutor(committedExecutor);
|
|
6548
|
-
const dataDir2 = process.env.SUVEREN_DATA_DIR ?? join19(
|
|
7950
|
+
const dataDir2 = process.env.SUVEREN_DATA_DIR ?? join19(homedir11(), ".suveren");
|
|
6549
7951
|
const executorLock = new ExecutorLock(dataDir2);
|
|
6550
7952
|
const lock = executorLock.acquire();
|
|
6551
7953
|
if (!lock.held) {
|