0sec-cli 0.23.0 → 0.24.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/0.js +66 -67
- package/README.md +37 -11
- package/chunks/{adapt-loop-V3JFWOKC.js → adapt-loop-L77BWMNE.js} +4 -4
- package/chunks/{artifact-scraper-NYKUF2M5.js → artifact-scraper-BV67RAP5.js} +2 -2
- package/chunks/{assumption-mining-ZX3YVWEH.js → assumption-mining-RPVR77A3.js} +18 -18
- package/chunks/{backend-connections-H52NIVH2.js → backend-connections-ACIXNYZN.js} +3 -3
- package/chunks/{chunk-MQJVU2GZ.js → chunk-2NBZZFRG.js} +79 -9
- package/chunks/{chunk-A2ADXAC5.js → chunk-2XGBEH53.js} +1288 -2313
- package/chunks/{chunk-3E54JYS4.js → chunk-3LPBJMHB.js} +1 -1
- package/chunks/{chunk-XORZUR2H.js → chunk-3WUPCZGF.js} +1 -1
- package/chunks/{chunk-65H3MRBF.js → chunk-4ROZFLSR.js} +1 -1
- package/chunks/chunk-56UTQKXO.js +56 -0
- package/chunks/{chunk-YYS2H33A.js → chunk-5ETI27Q3.js} +2 -2
- package/chunks/{chunk-3ZNI7F42.js → chunk-5NFIY2SJ.js} +3 -3
- package/chunks/{chunk-A5QO7N3S.js → chunk-5PFRE522.js} +1 -1
- package/chunks/{chunk-EH2WTNY7.js → chunk-5WAHHZBF.js} +1424 -859
- package/chunks/{chunk-WIYNYNL2.js → chunk-6XMFCVJS.js} +1 -1
- package/chunks/chunk-7UHAXEOV.js +289 -0
- package/chunks/{chunk-CP6HANBE.js → chunk-BD7WULFS.js} +4 -3
- package/chunks/{chunk-T7ZEVY5G.js → chunk-BMDG6T7D.js} +815 -41
- package/chunks/{chunk-QMICC5O2.js → chunk-BOBIAHKA.js} +2 -2
- package/chunks/{chunk-VDOWARMJ.js → chunk-BPU6M42R.js} +15 -1
- package/chunks/{chunk-UELXHK2Y.js → chunk-CGBDEST2.js} +16 -5
- package/chunks/{remote-workflow-runtime-RGQ5ECMN.js → chunk-CJRZSWII.js} +39 -8
- package/chunks/chunk-F2P25DJY.js +5421 -0
- package/chunks/{chunk-TO35Y326.js → chunk-GTED3FSZ.js} +5 -2
- package/chunks/{chunk-LT7BFGXA.js → chunk-GV5WXDHY.js} +10 -10
- package/chunks/{chunk-LTEE2JWK.js → chunk-GXXX7AGH.js} +251 -8
- package/chunks/chunk-GZA7RGK7.js +5198 -0
- package/chunks/{chunk-KHPOSLCF.js → chunk-H5QURBBK.js} +174 -710
- package/chunks/chunk-I43YYHDU.js +566 -0
- package/chunks/{chunk-62ENS6XO.js → chunk-IIBNQTOK.js} +3 -2
- package/chunks/{chunk-BF5FIK7H.js → chunk-IJ6UPW3H.js} +108 -24
- package/chunks/{chunk-TPUJZ74W.js → chunk-ILRMLA6J.js} +2 -2
- package/chunks/{chunk-I3OCJ2OA.js → chunk-JWSOUKFF.js} +5 -3
- package/chunks/{chunk-UZUELPFW.js → chunk-K74DRDC5.js} +1 -1
- package/chunks/chunk-LMP2T4G2.js +700 -0
- package/chunks/{chunk-K55B4OH4.js → chunk-NBV4I237.js} +1 -1
- package/chunks/chunk-OOMBOLAF.js +295 -0
- package/chunks/{chunk-YAEJAZYW.js → chunk-PJU24AKR.js} +2 -2
- package/chunks/{chunk-NI6QYQSK.js → chunk-PVJUPIQG.js} +6 -6
- package/chunks/{chunk-WYNZFNMF.js → chunk-Q7JKIKKF.js} +289 -10
- package/chunks/{chunk-ITES7BCY.js → chunk-QNRYOJEB.js} +3 -111
- package/chunks/{chunk-PIR6BNE7.js → chunk-R2XBH476.js} +2 -2
- package/chunks/{chunk-O6Z4XPOM.js → chunk-RURJYCWY.js} +1 -1
- package/chunks/chunk-RVJVM5L4.js +164 -0
- package/chunks/{chunk-NX4H6L4M.js → chunk-S2GITRD5.js} +2 -2
- package/chunks/{chunk-W5VNCB57.js → chunk-SU4IHQQI.js} +1 -1
- package/chunks/{chunk-V2BWXPZP.js → chunk-SZDD77KX.js} +6 -6
- package/chunks/{chunk-EJH2TNVH.js → chunk-TVS6WVSK.js} +3 -3
- package/chunks/{chunk-LVGZGG63.js → chunk-UJE4WOQS.js} +1 -1
- package/chunks/chunk-VU7V2SDW.js +284 -0
- package/chunks/chunk-W624J7NF.js +117 -0
- package/chunks/{chunk-OLSKXTQZ.js → chunk-WUPN3D37.js} +22 -248
- package/chunks/{chunk-HFDSIHTE.js → chunk-X2IPO5JO.js} +202 -30
- package/chunks/{chunk-JEYIWMGL.js → chunk-X2ZMSRTP.js} +1 -1
- package/chunks/{chunk-F344NYUN.js → chunk-XA2IBHPB.js} +1 -1
- package/chunks/{chunk-Y4HJRUFE.js → chunk-YERUDEXT.js} +876 -817
- package/chunks/{chunk-AAA4PRWK.js → chunk-YKWARO3T.js} +2 -2
- package/chunks/{chunk-P7KDN5PK.js → chunk-YSQWZ67M.js} +14 -14
- package/chunks/{chunk-4HGGNBKK.js → chunk-ZM6ANQZJ.js} +2593 -1563
- package/chunks/{codex-models-Y457ND6Q.js → codex-models-6XVNFRXZ.js} +7 -7
- package/chunks/{commands-PBDAOZKA.js → commands-FRPOFIO4.js} +1603 -6945
- package/chunks/dashboard-5HDSNBAU.js +76 -0
- package/chunks/{db-TPSI552V.js → db-BXJNWUYJ.js} +3 -3
- package/chunks/{deep-review-DKED6EN5.js → deep-review-GSJEBLCG.js} +34 -33
- package/chunks/{disclose-LDNKAVRH.js → disclose-OU6Z6GVT.js} +4 -2
- package/chunks/{dist-RLPNLRZD.js → dist-7KSYDDSG.js} +33 -1
- package/chunks/{dist-7Q5A6TQM.js → dist-RKLQIXDT.js} +18 -2
- package/chunks/{dist-4LGRIBOC.js → dist-TQMHMSBZ.js} +99 -64
- package/chunks/eval-runner-A2DIGVP7.js +31 -0
- package/chunks/{exploit-agent-RMFYNG7H.js → exploit-agent-Y2P4GFIY.js} +1 -1
- package/chunks/{exploit-autoclimb-2A2PQI4E.js → exploit-autoclimb-YRZ3TMS2.js} +1 -1
- package/chunks/{exploit-climb-DEJ232QN.js → exploit-climb-4LEY4SDW.js} +2 -2
- package/chunks/{fix-JGBW7XUN.js → fix-RQSIG75B.js} +7 -7
- package/chunks/{harness-CBOLMUHB.js → harness-3NVC3DDY.js} +3 -3
- package/chunks/{hunt-scan-MIRMABY4.js → hunt-scan-W246VWJC.js} +16 -16
- package/chunks/{kernel-primitive-NAPDMJC7.js → kernel-primitive-T47V3RD3.js} +2 -2
- package/chunks/{kernel-vm-runner-PR5OSDQ2.js → kernel-vm-runner-AEL5E3SV.js} +2 -2
- package/chunks/local-engine-ZCW4YC6E.js +21 -0
- package/chunks/managed-operation-6R2QPPW5.js +60 -0
- package/chunks/{memsafety-scan-RPS446CD.js → memsafety-scan-L2Z7E7TE.js} +2 -2
- package/chunks/{native-loop-CGOJMNHV.js → native-loop-26X2CHBV.js} +14 -13
- package/chunks/orchestrate-OBS37FWQ.js +63 -0
- package/chunks/{prepare-YVNENPNL.js → prepare-C3KGMBXT.js} +5 -5
- package/chunks/provider-model-discovery-QGUGL3XW.js +277 -0
- package/chunks/remote-workflow-runtime-DB52SINR.js +12 -0
- package/chunks/{run-VH4OIDKF.js → run-MU3MFL2Z.js} +902 -322
- package/chunks/{runtime-4TAJFVF7.js → runtime-AUBLQUOH.js} +2 -2
- package/chunks/{runtime-HWL6HG7A.js → runtime-U66JNSAY.js} +7 -7
- package/chunks/{scan-stream-HJAZJONE.js → scan-stream-34F3LTPV.js} +3 -2
- package/chunks/{scope-Q5PIX2IS.js → scope-HSTG63TQ.js} +2 -2
- package/chunks/service-plugins-5B23PWXC.js +64 -0
- package/chunks/{session-store-2FEIZGSV.js → session-store-MKAAP3WL.js} +2 -2
- package/chunks/{variant-candidates-TX5V6XOT.js → variant-candidates-LG767HWH.js} +7 -7
- package/chunks/{web-recon-prepass-6HOUGF7Q.js → web-recon-prepass-NJP6J3E4.js} +5 -5
- package/chunks/workflow-runtime-AR627I26.js +178 -0
- package/dashboard/assets/{abnfDiagram-VCTEODGH-BuvumFYR.js → abnfDiagram-VCTEODGH-Cq149Jpk.js} +1 -1
- package/dashboard/assets/{arc-DZzWDuCG.js → arc-CpP-3TRW.js} +1 -1
- package/dashboard/assets/architecture-7GRP2DOG-z5EHYkyH.js +1 -0
- package/dashboard/assets/{architectureDiagram-5GKGNRK7-Bw5el_EL.js → architectureDiagram-5GKGNRK7-ewtXpjR_.js} +1 -1
- package/dashboard/assets/{blockDiagram-I7D4REHJ-BjVLEApp.js → blockDiagram-I7D4REHJ-CZYkRRDi.js} +1 -1
- package/dashboard/assets/{c4Diagram-7LVT6UL2-B9nNzx2J.js → c4Diagram-7LVT6UL2-CV7Iu3_Z.js} +1 -1
- package/dashboard/assets/channel-CuYjfiC5.js +1 -0
- package/dashboard/assets/{chunk-4HAMMTFA-y72Q95nW.js → chunk-4HAMMTFA-PKCyW4m-.js} +1 -1
- package/dashboard/assets/{chunk-75Z2AOVW-BOhrOGMO.js → chunk-75Z2AOVW-C5MlFTc_.js} +1 -1
- package/dashboard/assets/{chunk-DU6HZSFF-B0EhAaNb.js → chunk-DU6HZSFF-3M37ulcO.js} +2 -2
- package/dashboard/assets/{chunk-F27PBJKO-BR-hiDvM.js → chunk-F27PBJKO-DTAok0qM.js} +1 -1
- package/dashboard/assets/{chunk-GMAD6QVW-H15vJYXg.js → chunk-GMAD6QVW-CPsr2F33.js} +1 -1
- package/dashboard/assets/{chunk-GVQU2GXP-BRndps0o.js → chunk-GVQU2GXP-C2AXGf9A.js} +1 -1
- package/dashboard/assets/{chunk-IMKFNOWR-CIzC_OWx.js → chunk-IMKFNOWR-DMG6yD_O.js} +1 -1
- package/dashboard/assets/{chunk-L3NEJ4N5-NR4P1Vko.js → chunk-L3NEJ4N5-BSvwdbXx.js} +1 -1
- package/dashboard/assets/{chunk-OSK3NFVY-DtjTDf8S.js → chunk-OSK3NFVY-CfB8V7FU.js} +1 -1
- package/dashboard/assets/{chunk-P2QGCYS3-CYT11Qjx.js → chunk-P2QGCYS3-CvmZvq6n.js} +1 -1
- package/dashboard/assets/{chunk-POPQ4Y6H-Duj2Ygwu.js → chunk-POPQ4Y6H-D_TvwDxL.js} +1 -1
- package/dashboard/assets/{chunk-PWAF6VOD-z4p5Tf_y.js → chunk-PWAF6VOD-Da5GnuOY.js} +1 -1
- package/dashboard/assets/{chunk-SHT3W25Y-DYeLYLS3.js → chunk-SHT3W25Y-CcsMJpR-.js} +1 -1
- package/dashboard/assets/{chunk-SVP7TREG-BKowxCVI.js → chunk-SVP7TREG-COojjieJ.js} +1 -1
- package/dashboard/assets/{chunk-TICWLB2K-GDAwEFlH.js → chunk-TICWLB2K-nBAmJwx6.js} +1 -1
- package/dashboard/assets/{chunk-XXDRQBXY-DRD79Mjx.js → chunk-XXDRQBXY-r5HyhWDv.js} +1 -1
- package/dashboard/assets/classDiagram-ZZMXUADV-DCuOrWUV.js +1 -0
- package/dashboard/assets/classDiagram-v2-VYDZK3BY-DCuOrWUV.js +1 -0
- package/dashboard/assets/{common-vnzd7UOF.js → common-C6snVPsk.js} +1 -1
- package/dashboard/assets/{cose-bilkent-JH36ORCC-GqCLsRyf.js → cose-bilkent-JH36ORCC-BLGcaZHk.js} +1 -1
- package/dashboard/assets/{cynefin-OW5HDTMX-CJN9YwWN.js → cynefin-OW5HDTMX-DbZMK00I.js} +1 -1
- package/dashboard/assets/{cynefinDiagram-5FMLGOSQ-DVJHCYQY.js → cynefinDiagram-5FMLGOSQ-xodkuQtW.js} +1 -1
- package/dashboard/assets/{dagre-GXQ25YYZ-BMCrrJJR.js → dagre-GXQ25YYZ-CRk9qfSG.js} +1 -1
- package/dashboard/assets/{diagram-S7CK7UJ4-CLYDdOEm.js → diagram-S7CK7UJ4-DNoTMlCO.js} +1 -1
- package/dashboard/assets/{diagram-UQ7AKVKN-CnPuAZK7.js → diagram-UQ7AKVKN-COccuyew.js} +1 -1
- package/dashboard/assets/{diagram-VSXAHHWV-Cr2I2DnG.js → diagram-VSXAHHWV-g6D1tWRb.js} +1 -1
- package/dashboard/assets/{diagram-VX7I27RA-_mGW1wpp.js → diagram-VX7I27RA-DhO4eVIC.js} +1 -1
- package/dashboard/assets/{diagram-Z3DM3KII-igybB0l9.js → diagram-Z3DM3KII-DeOQFexd.js} +1 -1
- package/dashboard/assets/{dist-Bb7_-x5y.js → dist-BQYeOQg3.js} +1 -1
- package/dashboard/assets/{ebnfDiagram-PWID7BFC-CVCICf2R.js → ebnfDiagram-PWID7BFC-MtkX2feM.js} +1 -1
- package/dashboard/assets/{erDiagram-RLTQ6QDP-CRjnfgxj.js → erDiagram-RLTQ6QDP-DIWSsoYZ.js} +1 -1
- package/dashboard/assets/eventmodeling-NTZA5JFV-D_3ez4Gv.js +1 -0
- package/dashboard/assets/flowDiagram-HODETNUW-D2boyYEd.js +1 -0
- package/dashboard/assets/{ganttDiagram-EL5Y4UJY-DppD7sD7.js → ganttDiagram-EL5Y4UJY-DKggkJe0.js} +1 -1
- package/dashboard/assets/{gitGraph-4MIJSDKK-Tw0vNnQc.js → gitGraph-4MIJSDKK-DFOQYwXc.js} +1 -1
- package/dashboard/assets/{gitGraphDiagram-WWUBYQGX-Cpf96-z3.js → gitGraphDiagram-WWUBYQGX-r_iR8Qzo.js} +1 -1
- package/dashboard/assets/{info-A6RAGUB7-DLyt9arx.js → info-A6RAGUB7-DeednLoB.js} +1 -1
- package/dashboard/assets/{infoDiagram-27XIBGKW-B6_piSNS.js → infoDiagram-27XIBGKW-ClQk7UyQ.js} +1 -1
- package/dashboard/assets/{ishikawaDiagram-5VMMS53U-DvMO0B0X.js → ishikawaDiagram-5VMMS53U-tR7Vziog.js} +1 -1
- package/dashboard/assets/{journeyDiagram-3NMN7TZE-f7ZK98b6.js → journeyDiagram-3NMN7TZE-Cpb9IqKL.js} +1 -1
- package/dashboard/assets/{kanban-definition-UXKFOSKX-Dso0nAxL.js → kanban-definition-UXKFOSKX-BzUuKbA9.js} +1 -1
- package/dashboard/assets/katex-BdBmVSus.js +257 -0
- package/dashboard/assets/{line-VcKaFG0G.js → line-BHY_tSHu.js} +1 -1
- package/dashboard/assets/{linear-D7YnpiWh.js → linear-naZZ1pHk.js} +1 -1
- package/dashboard/assets/{mermaid-parser.core-DriL2hrV.js → mermaid-parser.core-CPvd4cEx.js} +3 -3
- package/dashboard/assets/{mermaid.core-DJWYDh_P.js → mermaid.core-D_QsUPU8.js} +4 -4
- package/dashboard/assets/{mindmap-definition-YA3MSWOX-DZsoIeac.js → mindmap-definition-YA3MSWOX-CtfLTPPJ.js} +1 -1
- package/dashboard/assets/operations-BwCzhVnT.js +130 -0
- package/dashboard/assets/operations-QjKT0Hwi.css +1 -0
- package/dashboard/assets/{packet-AYTQ26CC-BQBIXKxk.js → packet-AYTQ26CC-DDQbkT9I.js} +1 -1
- package/dashboard/assets/{pegDiagram-XKGWAZYB-B0-lslgW.js → pegDiagram-XKGWAZYB-C1Dclbzj.js} +1 -1
- package/dashboard/assets/{pie-WAS4IAKB-DoY6dwQs.js → pie-WAS4IAKB-LpU4ULAs.js} +1 -1
- package/dashboard/assets/{pieDiagram-E7YTZNPT-ElmdRjp5.js → pieDiagram-E7YTZNPT-Cd7XwYo5.js} +1 -1
- package/dashboard/assets/{quadrantDiagram-AXDQQJYC-Ci1h98Q2.js → quadrantDiagram-AXDQQJYC-CaS-GQ4X.js} +1 -1
- package/dashboard/assets/{radar-RG4KPBEZ-DHod2WgB.js → radar-RG4KPBEZ-CcxW3NJm.js} +1 -1
- package/dashboard/assets/{railroad-74A4TZTK-90JxB4xV.js → railroad-74A4TZTK-9P5Rl8fP.js} +1 -1
- package/dashboard/assets/railroad-abnf-HS5TGJTU-CSfpwjna.js +1 -0
- package/dashboard/assets/railroad-ebnf-LZEXJU2U-D6R5qzr-.js +1 -0
- package/dashboard/assets/railroad-peg-WCYAUIDC-DKEX7ITG.js +1 -0
- package/dashboard/assets/{railroadDiagram-O6MQD6OU-k3190A6L.js → railroadDiagram-O6MQD6OU-BeesTrqn.js} +1 -1
- package/dashboard/assets/{requirementDiagram-BXWQKSXE-C3-eR7I5.js → requirementDiagram-BXWQKSXE-IjyK0DhW.js} +1 -1
- package/dashboard/assets/{sankeyDiagram-P5KCCOFB-Bc86pi8w.js → sankeyDiagram-P5KCCOFB-DmIluQqo.js} +1 -1
- package/dashboard/assets/{sequenceDiagram-WJ2MYXX4-Bv_MvEmz.js → sequenceDiagram-WJ2MYXX4-CUp8ZjTc.js} +1 -1
- package/dashboard/assets/{src-D7Dqq5yP.js → src-r61wDjcA.js} +1 -1
- package/dashboard/assets/{stateDiagram-D77RDMKH-Bq_GVmeW.js → stateDiagram-D77RDMKH-Dy9TQb8P.js} +1 -1
- package/dashboard/assets/stateDiagram-v2-MP3YSRHH-CQb0ZtPr.js +1 -0
- package/dashboard/assets/{swimlanes-42K2YHIH-q0iK-e0P.js → swimlanes-42K2YHIH-CZuDV7V5.js} +1 -1
- package/dashboard/assets/swimlanesDiagram-VR7AAH4N-BxgWMq8X.js +8 -0
- package/dashboard/assets/{timeline-definition-24CTP7MA-CNfF07hM.js → timeline-definition-24CTP7MA-BGdXW-iY.js} +1 -1
- package/dashboard/assets/{treeView-Q6P3EWNA-m629ePnS.js → treeView-Q6P3EWNA-DlRrayjM.js} +1 -1
- package/dashboard/assets/{treemap-WGGIJYW6-D9Cb2ETv.js → treemap-WGGIJYW6-C6UqLKfI.js} +1 -1
- package/dashboard/assets/{vennDiagram-4TSXK5OY-v4dKF-FE.js → vennDiagram-4TSXK5OY-DzKWHJsl.js} +1 -1
- package/dashboard/assets/{wardley-WFR3VGLG-DkwH_nRe.js → wardley-WFR3VGLG-thmyHKb0.js} +1 -1
- package/dashboard/assets/{wardleyDiagram-VM6X3IG4-3ImohVu4.js → wardleyDiagram-VM6X3IG4-DGsjYmNJ.js} +1 -1
- package/dashboard/assets/{xychartDiagram-S5SC5T6Z-QJu3dm7K.js → xychartDiagram-S5SC5T6Z-C7_T_tWt.js} +1 -1
- package/dashboard/index.html +3 -3
- package/package.json +3 -2
- package/worker-assets/demo-target/README.md +15 -0
- package/worker-assets/demo-target/include/decoder.h +27 -0
- package/worker-assets/demo-target/patched-decoder.c +40 -0
- package/worker-assets/demo-target/src/decoder.c +50 -0
- package/worker-assets/demo-target/witness-65537x1.bin +0 -0
- package/chunks/chunk-3P3XXSIJ.js +0 -463
- package/chunks/chunk-EWXOG6D3.js +0 -1695
- package/chunks/chunk-FXR3BT2G.js +0 -226
- package/chunks/chunk-R35KKJA4.js +0 -90
- package/chunks/eval-runner-RZKDOQ2G.js +0 -30
- package/chunks/managed-operation-YNQQEA5V.js +0 -59
- package/chunks/orchestrate-5NK34H3W.js +0 -62
- package/chunks/service-plugins-QYFITODK.js +0 -63
- package/chunks/workflow-runtime-ZEEBJFQG.js +0 -64
- package/dashboard/assets/architecture-7GRP2DOG-DK9bv1Md.js +0 -1
- package/dashboard/assets/channel-Ck17eKdX.js +0 -1
- package/dashboard/assets/classDiagram-ZZMXUADV-BaeZA6LE.js +0 -1
- package/dashboard/assets/classDiagram-v2-VYDZK3BY-BaeZA6LE.js +0 -1
- package/dashboard/assets/eventmodeling-NTZA5JFV-po5oDydv.js +0 -1
- package/dashboard/assets/flowDiagram-HODETNUW-TSV9s7q4.js +0 -1
- package/dashboard/assets/katex-CXMH3UgJ.js +0 -257
- package/dashboard/assets/operations-BIaUSnYv.js +0 -121
- package/dashboard/assets/operations-BeCdkT4S.css +0 -1
- package/dashboard/assets/railroad-abnf-HS5TGJTU-BkXGl114.js +0 -1
- package/dashboard/assets/railroad-ebnf-LZEXJU2U-QVAERn2C.js +0 -1
- package/dashboard/assets/railroad-peg-WCYAUIDC-B1-PM01z.js +0 -1
- package/dashboard/assets/stateDiagram-v2-MP3YSRHH-z1XRaftz.js +0 -1
- package/dashboard/assets/swimlanesDiagram-VR7AAH4N-C9L6XBQj.js +0 -8
- package/dashboard/assets/zero-connect-BGDY0PMZ.webp +0 -0
- package/dashboard/assets/zero-curious-DovTLOs8.webp +0 -0
- package/dashboard/assets/zero-peek-Dzm3MT3X.png +0 -0
- package/dashboard/assets/zero-planning-CBeHDrG_.webp +0 -0
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
import { createRequire as __0CreateRequire } from "node:module";
|
|
3
3
|
const require = __0CreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
|
+
ImpactAssessmentSchema,
|
|
6
|
+
compareFindingsByBusinessPriority,
|
|
7
|
+
getFindingPriority,
|
|
5
8
|
homeStateDir,
|
|
6
9
|
parseSecurityWorkflowInput,
|
|
7
10
|
runStateDir
|
|
8
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-GXXX7AGH.js";
|
|
9
12
|
import {
|
|
10
13
|
__export
|
|
11
14
|
} from "./chunk-VCSFCHAJ.js";
|
|
@@ -153,24 +156,55 @@ function createWasmEngine(path) {
|
|
|
153
156
|
const sqliteWasm = createRequire(import.meta.url)("node-sqlite3-wasm");
|
|
154
157
|
const { Database: WasmDatabaseCtor } = sqliteWasm;
|
|
155
158
|
const wasm = new WasmDatabaseCtor(path);
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
159
|
+
const prepared = /* @__PURE__ */ new Set();
|
|
160
|
+
const wrapStatement = (sql) => {
|
|
161
|
+
let initial = wasm.prepare(sql);
|
|
162
|
+
prepared.add(initial);
|
|
163
|
+
const execute = (operation) => {
|
|
164
|
+
if (!wasm.isOpen)
|
|
165
|
+
throw new Error("Database already closed");
|
|
166
|
+
const statement = initial ?? wasm.prepare(sql);
|
|
167
|
+
initial = void 0;
|
|
168
|
+
prepared.add(statement);
|
|
169
|
+
let succeeded = false;
|
|
170
|
+
try {
|
|
171
|
+
const result = operation(statement);
|
|
172
|
+
succeeded = true;
|
|
173
|
+
return result;
|
|
174
|
+
} finally {
|
|
175
|
+
prepared.delete(statement);
|
|
176
|
+
try {
|
|
177
|
+
statement.finalize();
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (succeeded)
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
run: (bind) => execute((statement) => statement.run(bind)),
|
|
186
|
+
get: (bind) => execute((statement) => {
|
|
187
|
+
const row = statement.get(bind);
|
|
188
|
+
return row == null ? void 0 : row;
|
|
189
|
+
}),
|
|
190
|
+
all: (bind) => execute((statement) => statement.all(bind)),
|
|
191
|
+
values: (bind) => execute((statement) => {
|
|
192
|
+
const rows = statement.all(bind);
|
|
193
|
+
return rows.map((row) => Object.values(row));
|
|
194
|
+
})
|
|
195
|
+
};
|
|
196
|
+
};
|
|
168
197
|
return {
|
|
169
|
-
prepare: (sql) => wrapStatement(
|
|
198
|
+
prepare: (sql) => wrapStatement(sql),
|
|
170
199
|
exec: (sql) => {
|
|
171
200
|
wasm.exec(sql);
|
|
172
201
|
},
|
|
173
|
-
close: () =>
|
|
202
|
+
close: () => {
|
|
203
|
+
for (const statement of prepared)
|
|
204
|
+
statement.finalize();
|
|
205
|
+
prepared.clear();
|
|
206
|
+
wasm.close();
|
|
207
|
+
},
|
|
174
208
|
get inTransaction() {
|
|
175
209
|
return wasm.inTransaction;
|
|
176
210
|
},
|
|
@@ -625,6 +659,144 @@ var trustGraphEdges = sqliteTable("trust_graph_edges", {
|
|
|
625
659
|
index("idx_tge_relation").on(table.relation)
|
|
626
660
|
]);
|
|
627
661
|
|
|
662
|
+
// packages/db/dist/finding-priority.js
|
|
663
|
+
function* iterateLatestFindingMetadata(sqlite, opts = {}) {
|
|
664
|
+
let afterId = null;
|
|
665
|
+
while (true) {
|
|
666
|
+
const page = sqlite.prepare(`SELECT f.id,COALESCE(f.fingerprint,f.id) AS familyKey,f.timestamp,f.severity,f.category,f.status,f.triageStatus,f.cvssScore,
|
|
667
|
+
CASE WHEN length(CAST(f.impactAssessment AS BLOB)) <= ${MAX_ASSESSMENT_BYTES} THEN f.impactAssessment ELSE NULL END AS impactAssessment
|
|
668
|
+
FROM findings f WHERE (@afterId IS NULL OR f.id>@afterId) AND (@scanId IS NULL OR f.scanId=@scanId)
|
|
669
|
+
AND NOT EXISTS (SELECT 1 FROM findings newer WHERE COALESCE(newer.fingerprint,newer.id)=COALESCE(f.fingerprint,f.id)
|
|
670
|
+
AND (@scanId IS NULL OR newer.scanId=@scanId) AND (newer.timestamp>f.timestamp OR (newer.timestamp=f.timestamp AND newer.id>f.id)))
|
|
671
|
+
AND (@severity IS NULL OR f.severity=@severity) AND (@category IS NULL OR f.category=@category)
|
|
672
|
+
AND (@status IS NULL OR f.status=@status) AND (@triageStatus IS NULL OR f.triageStatus=@triageStatus)
|
|
673
|
+
ORDER BY f.id LIMIT ${PAGE_SIZE}`).all({
|
|
674
|
+
afterId,
|
|
675
|
+
scanId: opts.scanId ?? null,
|
|
676
|
+
severity: opts.severity ?? null,
|
|
677
|
+
category: opts.category ?? null,
|
|
678
|
+
status: opts.status ?? null,
|
|
679
|
+
triageStatus: opts.triageStatus ?? null
|
|
680
|
+
});
|
|
681
|
+
yield* page;
|
|
682
|
+
if (page.length < PAGE_SIZE)
|
|
683
|
+
return;
|
|
684
|
+
afterId = page[page.length - 1].id;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function getFindingFamilyMetadata(sqlite, key, opts = {}) {
|
|
688
|
+
const rows = sqlite.prepare(`SELECT scanId,COUNT(*) AS count FROM findings WHERE COALESCE(fingerprint,id)=@key
|
|
689
|
+
AND (@scanId IS NULL OR scanId=@scanId) GROUP BY scanId ORDER BY scanId`).all({ key, scanId: opts.scanId ?? null });
|
|
690
|
+
return { count: rows.reduce((sum, row) => sum + row.count, 0), scanIds: rows.map((row) => row.scanId) };
|
|
691
|
+
}
|
|
692
|
+
function compare(a, b) {
|
|
693
|
+
return b.businessPriorityRank - a.businessPriorityRank || compareFindingsByBusinessPriority(a, b) || b.timestamp - a.timestamp || b.id.localeCompare(a.id);
|
|
694
|
+
}
|
|
695
|
+
function retain(heap, item, limit2) {
|
|
696
|
+
if (heap.length < limit2) {
|
|
697
|
+
heap.push(item);
|
|
698
|
+
let index3 = heap.length - 1;
|
|
699
|
+
while (index3 > 0) {
|
|
700
|
+
const parent = Math.floor((index3 - 1) / 2);
|
|
701
|
+
if (compare(heap[index3], heap[parent]) <= 0)
|
|
702
|
+
break;
|
|
703
|
+
[heap[index3], heap[parent]] = [heap[parent], heap[index3]];
|
|
704
|
+
index3 = parent;
|
|
705
|
+
}
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (compare(item, heap[0]) >= 0)
|
|
709
|
+
return;
|
|
710
|
+
heap[0] = item;
|
|
711
|
+
let index2 = 0;
|
|
712
|
+
while (true) {
|
|
713
|
+
let worst = index2;
|
|
714
|
+
for (const child of [index2 * 2 + 1, index2 * 2 + 2])
|
|
715
|
+
if (child < heap.length && compare(heap[child], heap[worst]) > 0)
|
|
716
|
+
worst = child;
|
|
717
|
+
if (worst === index2)
|
|
718
|
+
break;
|
|
719
|
+
[heap[index2], heap[worst]] = [heap[worst], heap[index2]];
|
|
720
|
+
index2 = worst;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
var PAGE_SIZE = 500;
|
|
724
|
+
var MAX_ASSESSMENT_BYTES = 80 * 1024;
|
|
725
|
+
function rankedFindingRows(sqlite, opts, latestOnly) {
|
|
726
|
+
const limit2 = opts.limit ?? 100;
|
|
727
|
+
if (!Number.isSafeInteger(limit2) || limit2 < 0 || limit2 > 5e3)
|
|
728
|
+
throw new Error("Business-priority finding limit must be an integer between 0 and 5000.");
|
|
729
|
+
if (limit2 === 0)
|
|
730
|
+
return [];
|
|
731
|
+
const heap = [];
|
|
732
|
+
let afterId = null;
|
|
733
|
+
const statement = sqlite.prepare(`
|
|
734
|
+
SELECT f.id, COALESCE(f.fingerprint, f.id) AS familyKey, f.timestamp, f.severity, f.cvssScore,
|
|
735
|
+
CASE WHEN length(CAST(f.impactAssessment AS BLOB)) <= ${MAX_ASSESSMENT_BYTES} THEN f.impactAssessment ELSE NULL END AS impactAssessment
|
|
736
|
+
FROM findings f
|
|
737
|
+
WHERE (@afterId IS NULL OR f.id > @afterId)
|
|
738
|
+
AND (@scanId IS NULL OR f.scanId = @scanId)
|
|
739
|
+
AND (@latestOnly = 0 OR NOT EXISTS (SELECT 1 FROM findings newer
|
|
740
|
+
WHERE COALESCE(newer.fingerprint, newer.id) = COALESCE(f.fingerprint, f.id)
|
|
741
|
+
AND (@scanId IS NULL OR newer.scanId = @scanId)
|
|
742
|
+
AND (newer.timestamp > f.timestamp OR (newer.timestamp = f.timestamp AND newer.id > f.id))))
|
|
743
|
+
AND (@severity IS NULL OR f.severity = @severity)
|
|
744
|
+
AND (@category IS NULL OR f.category = @category)
|
|
745
|
+
AND (@status IS NULL OR f.status = @status)
|
|
746
|
+
AND (@triageStatus IS NULL OR f.triageStatus = @triageStatus)
|
|
747
|
+
ORDER BY f.id LIMIT ${PAGE_SIZE}`);
|
|
748
|
+
while (true) {
|
|
749
|
+
const page = statement.all({ latestOnly: latestOnly ? 1 : 0, afterId, scanId: opts.scanId ?? null, severity: opts.severity ?? null, category: opts.category ?? null, status: opts.status ?? null, triageStatus: opts.triageStatus ?? null });
|
|
750
|
+
for (const row of page) {
|
|
751
|
+
let impactAssessment;
|
|
752
|
+
try {
|
|
753
|
+
impactAssessment = row.impactAssessment ? JSON.parse(row.impactAssessment) : void 0;
|
|
754
|
+
} catch {
|
|
755
|
+
}
|
|
756
|
+
const { impactAssessment: _serialized, ...metadata } = row;
|
|
757
|
+
retain(heap, { ...metadata, businessPriorityRank: getFindingPriority({ impactAssessment }).rank }, limit2);
|
|
758
|
+
}
|
|
759
|
+
if (page.length < PAGE_SIZE)
|
|
760
|
+
break;
|
|
761
|
+
afterId = page[page.length - 1].id;
|
|
762
|
+
}
|
|
763
|
+
const selected = heap.sort(compare);
|
|
764
|
+
const rows = /* @__PURE__ */ new Map();
|
|
765
|
+
for (let offset = 0; offset < selected.length; offset += 300) {
|
|
766
|
+
const ids = selected.slice(offset, offset + 300).map((item) => item.id);
|
|
767
|
+
for (const row of sqlite.prepare(`SELECT * FROM findings WHERE id IN (${ids.map(() => "?").join(",")})`).all(...ids))
|
|
768
|
+
rows.set(row.id, row);
|
|
769
|
+
}
|
|
770
|
+
return selected.map((item) => rows.get(item.id));
|
|
771
|
+
}
|
|
772
|
+
function selectFindingsByBusinessPriority(sqlite, opts = {}) {
|
|
773
|
+
return sqlite.transaction(() => rankedFindingRows(sqlite, opts, false))();
|
|
774
|
+
}
|
|
775
|
+
function selectFindingFamiliesByBusinessPriority(sqlite, opts = {}) {
|
|
776
|
+
return sqlite.transaction(() => {
|
|
777
|
+
return rankedFindingRows(sqlite, opts, true).map((item) => {
|
|
778
|
+
const params = { key: item.fingerprint ?? item.id, scanId: opts.scanId ?? null };
|
|
779
|
+
const counts = sqlite.prepare(`SELECT COUNT(*) AS count, COUNT(DISTINCT scanId) AS scanCount FROM findings
|
|
780
|
+
WHERE COALESCE(fingerprint,id)=@key AND (@scanId IS NULL OR scanId=@scanId)`).get(params);
|
|
781
|
+
const verdictCounts = sqlite.prepare(`SELECT COUNT(*) AS total,
|
|
782
|
+
COALESCE(SUM(CASE WHEN v.verdict='TRUE_POSITIVE' THEN 1 ELSE 0 END),0) AS truePositive,
|
|
783
|
+
COALESCE(SUM(CASE WHEN v.verdict='FALSE_POSITIVE' THEN 1 ELSE 0 END),0) AS falsePositive,
|
|
784
|
+
COALESCE(SUM(CASE WHEN v.verdict NOT IN ('TRUE_POSITIVE','FALSE_POSITIVE') THEN 1 ELSE 0 END),0) AS unsure
|
|
785
|
+
FROM verdicts v JOIN findings f ON f.id=v.findingId
|
|
786
|
+
WHERE COALESCE(f.fingerprint,f.id)=@key AND (@scanId IS NULL OR f.scanId=@scanId)`).get(params);
|
|
787
|
+
const activeSessions = sqlite.prepare(`SELECT s.id,s.scanId,s.status,s.agentRole,s.createdAt,s.updatedAt FROM agent_sessions s
|
|
788
|
+
WHERE s.status='running' AND EXISTS (SELECT 1 FROM findings f WHERE f.scanId=s.scanId
|
|
789
|
+
AND COALESCE(f.fingerprint,f.id)=@key AND (@scanId IS NULL OR f.scanId=@scanId))
|
|
790
|
+
AND NOT EXISTS (SELECT 1 FROM agent_sessions newer WHERE newer.status='running' AND newer.agentRole=s.agentRole
|
|
791
|
+
AND (newer.updatedAt>s.updatedAt OR (newer.updatedAt=s.updatedAt AND newer.id>s.id))
|
|
792
|
+
AND EXISTS (SELECT 1 FROM findings f WHERE f.scanId=newer.scanId
|
|
793
|
+
AND COALESCE(f.fingerprint,f.id)=@key AND (@scanId IS NULL OR f.scanId=@scanId)))
|
|
794
|
+
ORDER BY s.agentRole`).all(params);
|
|
795
|
+
return { key: item.fingerprint ?? item.id, latest: item, ...counts, verdictCounts, activeSessions };
|
|
796
|
+
});
|
|
797
|
+
})();
|
|
798
|
+
}
|
|
799
|
+
|
|
628
800
|
// packages/db/dist/database.js
|
|
629
801
|
var DEFAULT_DB_DIR = homeStateDir();
|
|
630
802
|
var DEFAULT_DB_PATH = join(DEFAULT_DB_DIR, "0.db");
|
|
@@ -763,10 +935,13 @@ function clearStaleLockIfAny(path) {
|
|
|
763
935
|
}
|
|
764
936
|
}
|
|
765
937
|
var osecDB = class {
|
|
938
|
+
/** Resolved storage identity for scoped stores sharing this database. */
|
|
939
|
+
databasePath;
|
|
766
940
|
sqlite;
|
|
767
941
|
db;
|
|
768
942
|
constructor(dbPath, options = {}) {
|
|
769
943
|
const path = resolveOsecDbPath(dbPath);
|
|
944
|
+
this.databasePath = path;
|
|
770
945
|
if (options.readOnly) {
|
|
771
946
|
if (path !== ":memory:" && !existsSync2(path)) {
|
|
772
947
|
throw new Error(`Database does not exist: ${path}`);
|
|
@@ -935,13 +1110,13 @@ var osecDB = class {
|
|
|
935
1110
|
params.category = opts.category;
|
|
936
1111
|
}
|
|
937
1112
|
const whereSql = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
|
|
938
|
-
const
|
|
1113
|
+
const limit2 = opts?.limit ?? 500;
|
|
939
1114
|
const rows = this.sqlite.prepare(`SELECT id, scope, scope_value as scopeValue, category, pattern, reasoning,
|
|
940
1115
|
created_at as createdAt, applied_count as appliedCount
|
|
941
1116
|
FROM triage_memories
|
|
942
1117
|
${whereSql}
|
|
943
1118
|
ORDER BY applied_count DESC, created_at DESC
|
|
944
|
-
LIMIT ${
|
|
1119
|
+
LIMIT ${limit2}`).all(params);
|
|
945
1120
|
return rows;
|
|
946
1121
|
}
|
|
947
1122
|
deleteTriageMemory(id) {
|
|
@@ -1320,8 +1495,8 @@ var osecDB = class {
|
|
|
1320
1495
|
getScan(scanId) {
|
|
1321
1496
|
return this.db.select().from(scans).where(eq(scans.id, scanId)).get();
|
|
1322
1497
|
}
|
|
1323
|
-
listScans(
|
|
1324
|
-
return this.db.select().from(scans).orderBy(desc(scans.startedAt)).limit(
|
|
1498
|
+
listScans(limit2 = 20) {
|
|
1499
|
+
return this.db.select().from(scans).orderBy(desc(scans.startedAt)).limit(limit2).all();
|
|
1325
1500
|
}
|
|
1326
1501
|
listScansByTarget(target, opts) {
|
|
1327
1502
|
const query = this.db.select().from(scans).where(eq(scans.target, target)).orderBy(desc(scans.startedAt));
|
|
@@ -1475,6 +1650,24 @@ var osecDB = class {
|
|
|
1475
1650
|
getLatestFindingForScan(scanId) {
|
|
1476
1651
|
return this.db.select().from(findings).where(eq(findings.scanId, scanId)).orderBy(desc(findings.timestamp)).get();
|
|
1477
1652
|
}
|
|
1653
|
+
/** Stable bounded keyset projection for read-only external findings APIs. */
|
|
1654
|
+
listFindingsApiPage(options) {
|
|
1655
|
+
if (!Number.isSafeInteger(options.limit) || options.limit < 1 || options.limit > 101)
|
|
1656
|
+
throw new Error("Finding API page limit must be between 1 and 101.");
|
|
1657
|
+
return this.sqlite.prepare(`SELECT id, scanId, timestamp FROM findings
|
|
1658
|
+
WHERE (@scanId IS NULL OR scanId = @scanId) AND (@severity IS NULL OR severity = @severity)
|
|
1659
|
+
AND (@status IS NULL OR status = @status) AND (@includeSuppressed = 1 OR COALESCE(triageStatus, 'new') != 'suppressed')
|
|
1660
|
+
AND (@beforeTimestamp IS NULL OR timestamp < @beforeTimestamp OR (timestamp = @beforeTimestamp AND id < @beforeId))
|
|
1661
|
+
ORDER BY timestamp DESC, id DESC LIMIT @limit`).all({
|
|
1662
|
+
limit: options.limit,
|
|
1663
|
+
scanId: options.scanId ?? null,
|
|
1664
|
+
severity: options.severity ?? null,
|
|
1665
|
+
status: options.status ?? null,
|
|
1666
|
+
includeSuppressed: options.includeSuppressed ? 1 : 0,
|
|
1667
|
+
beforeTimestamp: options.before?.timestamp ?? null,
|
|
1668
|
+
beforeId: options.before?.id ?? null
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1478
1671
|
listFindings(opts) {
|
|
1479
1672
|
const conditions = [];
|
|
1480
1673
|
if (opts?.scanId)
|
|
@@ -1493,6 +1686,21 @@ var osecDB = class {
|
|
|
1493
1686
|
}
|
|
1494
1687
|
return query.all();
|
|
1495
1688
|
}
|
|
1689
|
+
/** Globally rank current family assessments before the body/row limit. Historical review counts remain intact. */
|
|
1690
|
+
listFindingFamiliesByBusinessPriority(opts) {
|
|
1691
|
+
return selectFindingFamiliesByBusinessPriority(this.sqlite, opts);
|
|
1692
|
+
}
|
|
1693
|
+
/** Bounded row-level native --all list; historical rows are independently ranked. */
|
|
1694
|
+
listFindingsByBusinessPriority(opts) {
|
|
1695
|
+
return selectFindingsByBusinessPriority(this.sqlite, opts);
|
|
1696
|
+
}
|
|
1697
|
+
/** Uncapped paged metadata for exact cross-database latest-family merging; no evidence bodies. */
|
|
1698
|
+
iterateLatestFindingMetadata(opts) {
|
|
1699
|
+
return iterateLatestFindingMetadata(this.sqlite, opts);
|
|
1700
|
+
}
|
|
1701
|
+
getFindingFamilyMetadata(key, opts) {
|
|
1702
|
+
return getFindingFamilyMetadata(this.sqlite, key, opts);
|
|
1703
|
+
}
|
|
1496
1704
|
/** Alias for listFindings — backward compat with core agent tools */
|
|
1497
1705
|
queryFindings(opts) {
|
|
1498
1706
|
return this.listFindings(opts);
|
|
@@ -1503,6 +1711,12 @@ var osecDB = class {
|
|
|
1503
1711
|
if (finding?.fingerprint)
|
|
1504
1712
|
this.syncFindingGraph(finding.scanId, finding.fingerprint);
|
|
1505
1713
|
}
|
|
1714
|
+
/** Operator business context is metadata; never changes verification or execution authority. */
|
|
1715
|
+
updateFindingImpactAssessment(findingId, assessment) {
|
|
1716
|
+
const parsed = assessment === null ? null : ImpactAssessmentSchema.parse(assessment);
|
|
1717
|
+
const result = this.db.update(findings).set({ impactAssessment: parsed === null ? null : JSON.stringify(parsed) }).where(eq(findings.id, findingId)).run();
|
|
1718
|
+
return result.changes > 0;
|
|
1719
|
+
}
|
|
1506
1720
|
saveFindingPocExecution(findingId, execution) {
|
|
1507
1721
|
const serialized = JSON.stringify(execution);
|
|
1508
1722
|
this.db.update(findings).set({ pocExecution: serialized }).where(eq(findings.id, findingId)).run();
|
|
@@ -1687,7 +1901,7 @@ var osecDB = class {
|
|
|
1687
1901
|
conditions.push(eq(pipelineEvents.eventType, opts.eventType));
|
|
1688
1902
|
return this.db.select().from(pipelineEvents).where(and(...conditions)).orderBy(pipelineEvents.timestamp).all();
|
|
1689
1903
|
}
|
|
1690
|
-
listRecentEvents(
|
|
1904
|
+
listRecentEvents(limit2 = 50) {
|
|
1691
1905
|
return this.db.select({
|
|
1692
1906
|
id: pipelineEvents.id,
|
|
1693
1907
|
scanId: pipelineEvents.scanId,
|
|
@@ -1700,13 +1914,13 @@ var osecDB = class {
|
|
|
1700
1914
|
source: pipelineEvents.source,
|
|
1701
1915
|
payload: pipelineEvents.payload,
|
|
1702
1916
|
timestamp: pipelineEvents.timestamp
|
|
1703
|
-
}).from(pipelineEvents).innerJoin(scans, eq(pipelineEvents.scanId, scans.id)).leftJoin(findings, eq(pipelineEvents.findingId, findings.id)).orderBy(desc(pipelineEvents.timestamp)).limit(
|
|
1917
|
+
}).from(pipelineEvents).innerJoin(scans, eq(pipelineEvents.scanId, scans.id)).leftJoin(findings, eq(pipelineEvents.findingId, findings.id)).orderBy(desc(pipelineEvents.timestamp)).limit(limit2).all();
|
|
1704
1918
|
}
|
|
1705
1919
|
/**
|
|
1706
1920
|
* Read persisted events strictly after a durable `(timestamp, id)` cursor.
|
|
1707
1921
|
* The UUID tie-breaker makes same-millisecond multi-process writes stable.
|
|
1708
1922
|
*/
|
|
1709
|
-
listEventsAfter(cursor,
|
|
1923
|
+
listEventsAfter(cursor, limit2 = 250) {
|
|
1710
1924
|
const selectEvents = () => this.db.select({
|
|
1711
1925
|
id: pipelineEvents.id,
|
|
1712
1926
|
scanId: pipelineEvents.scanId,
|
|
@@ -1721,9 +1935,9 @@ var osecDB = class {
|
|
|
1721
1935
|
timestamp: pipelineEvents.timestamp
|
|
1722
1936
|
}).from(pipelineEvents).innerJoin(scans, eq(pipelineEvents.scanId, scans.id)).leftJoin(findings, eq(pipelineEvents.findingId, findings.id));
|
|
1723
1937
|
if (!cursor) {
|
|
1724
|
-
return selectEvents().orderBy(asc(pipelineEvents.timestamp), asc(pipelineEvents.id)).limit(
|
|
1938
|
+
return selectEvents().orderBy(asc(pipelineEvents.timestamp), asc(pipelineEvents.id)).limit(limit2).all();
|
|
1725
1939
|
}
|
|
1726
|
-
return selectEvents().where(or(gt(pipelineEvents.timestamp, cursor.timestamp), and(eq(pipelineEvents.timestamp, cursor.timestamp), gt(pipelineEvents.id, cursor.id)))).orderBy(asc(pipelineEvents.timestamp), asc(pipelineEvents.id)).limit(
|
|
1940
|
+
return selectEvents().where(or(gt(pipelineEvents.timestamp, cursor.timestamp), and(eq(pipelineEvents.timestamp, cursor.timestamp), gt(pipelineEvents.id, cursor.id)))).orderBy(asc(pipelineEvents.timestamp), asc(pipelineEvents.id)).limit(limit2).all();
|
|
1727
1941
|
}
|
|
1728
1942
|
// ── Agent Sessions (resumable state) ──
|
|
1729
1943
|
saveSession(session) {
|
|
@@ -1795,8 +2009,8 @@ var osecDB = class {
|
|
|
1795
2009
|
getCase(caseId) {
|
|
1796
2010
|
return this.db.select().from(cases).where(eq(cases.id, caseId)).get();
|
|
1797
2011
|
}
|
|
1798
|
-
listCases(
|
|
1799
|
-
return this.db.select().from(cases).orderBy(desc(cases.updatedAt)).limit(
|
|
2012
|
+
listCases(limit2 = 100) {
|
|
2013
|
+
return this.db.select().from(cases).orderBy(desc(cases.updatedAt)).limit(limit2).all();
|
|
1800
2014
|
}
|
|
1801
2015
|
upsertWorkItem(record) {
|
|
1802
2016
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1932,8 +2146,8 @@ var osecDB = class {
|
|
|
1932
2146
|
}
|
|
1933
2147
|
}).run();
|
|
1934
2148
|
}
|
|
1935
|
-
listWorkers(
|
|
1936
|
-
return this.db.select().from(workers).orderBy(desc(workers.heartbeatAt)).limit(
|
|
2149
|
+
listWorkers(limit2 = 50) {
|
|
2150
|
+
return this.db.select().from(workers).orderBy(desc(workers.heartbeatAt)).limit(limit2).all();
|
|
1937
2151
|
}
|
|
1938
2152
|
stopWorkersByLabel(label, exceptId) {
|
|
1939
2153
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -2035,8 +2249,8 @@ var osecDB = class {
|
|
|
2035
2249
|
params.push(query.target);
|
|
2036
2250
|
}
|
|
2037
2251
|
const whereSql = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
|
|
2038
|
-
const
|
|
2039
|
-
return this.sqlite.prepare(`SELECT * FROM persistent_credentials ${whereSql} ORDER BY lastSeenAt DESC LIMIT ${
|
|
2252
|
+
const limit2 = query.limit ?? 500;
|
|
2253
|
+
return this.sqlite.prepare(`SELECT * FROM persistent_credentials ${whereSql} ORDER BY lastSeenAt DESC LIMIT ${limit2}`).all(...params);
|
|
2040
2254
|
}
|
|
2041
2255
|
// ── Trust graph edges (0#771) ──
|
|
2042
2256
|
/**
|
|
@@ -2117,8 +2331,8 @@ var osecDB = class {
|
|
|
2117
2331
|
params.push(query.relation);
|
|
2118
2332
|
}
|
|
2119
2333
|
const whereSql = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
|
|
2120
|
-
const
|
|
2121
|
-
return this.sqlite.prepare(`SELECT * FROM trust_graph_edges ${whereSql} ORDER BY updatedAt DESC LIMIT ${
|
|
2334
|
+
const limit2 = query.limit ?? 500;
|
|
2335
|
+
return this.sqlite.prepare(`SELECT * FROM trust_graph_edges ${whereSql} ORDER BY updatedAt DESC LIMIT ${limit2}`).all(...params);
|
|
2122
2336
|
}
|
|
2123
2337
|
// ── Utilities ──
|
|
2124
2338
|
close() {
|
|
@@ -2485,6 +2699,7 @@ CREATE INDEX IF NOT EXISTS idx_findings_severity ON findings(severity);
|
|
|
2485
2699
|
CREATE INDEX IF NOT EXISTS idx_findings_category ON findings(category);
|
|
2486
2700
|
CREATE INDEX IF NOT EXISTS idx_findings_status ON findings(status);
|
|
2487
2701
|
CREATE INDEX IF NOT EXISTS idx_findings_fingerprint ON findings(fingerprint);
|
|
2702
|
+
CREATE INDEX IF NOT EXISTS idx_findings_priority_latest ON findings(COALESCE(fingerprint,id),timestamp DESC,id DESC);
|
|
2488
2703
|
CREATE INDEX IF NOT EXISTS idx_findings_triageStatus ON findings(triageStatus);
|
|
2489
2704
|
CREATE INDEX IF NOT EXISTS idx_findings_workflowStatus ON findings(workflowStatus);
|
|
2490
2705
|
CREATE INDEX IF NOT EXISTS idx_attack_results_scanId ON attack_results(scanId);
|
|
@@ -2616,7 +2831,447 @@ function writeOsecRunReport(storage, report) {
|
|
|
2616
2831
|
}
|
|
2617
2832
|
|
|
2618
2833
|
// packages/db/dist/security-workflows.js
|
|
2619
|
-
import { randomUUID as
|
|
2834
|
+
import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
|
|
2835
|
+
|
|
2836
|
+
// packages/db/dist/learning.js
|
|
2837
|
+
import { randomUUID as randomUUID3, createHash as createHash2 } from "node:crypto";
|
|
2838
|
+
import { realpathSync } from "node:fs";
|
|
2839
|
+
import { isAbsolute as isAbsolute2, resolve as resolve3 } from "node:path";
|
|
2840
|
+
import { homedir } from "node:os";
|
|
2841
|
+
var LearningStoreError = class extends Error {
|
|
2842
|
+
statusCode;
|
|
2843
|
+
constructor(message, statusCode = 400) {
|
|
2844
|
+
super(message);
|
|
2845
|
+
this.statusCode = statusCode;
|
|
2846
|
+
this.name = "LearningStoreError";
|
|
2847
|
+
}
|
|
2848
|
+
};
|
|
2849
|
+
function learningProjectId(target) {
|
|
2850
|
+
let identity = target.trim();
|
|
2851
|
+
const sourceTarget = identity.startsWith("source:");
|
|
2852
|
+
const path = sourceTarget ? identity.slice("source:".length).trim() : identity;
|
|
2853
|
+
const remote = /^[a-z][a-z0-9+.-]*:\/\//i.test(path);
|
|
2854
|
+
if (!remote && path && (sourceTarget || isAbsolute2(path) || /^\.{1,2}(?:\/|$)/.test(path) || path.startsWith("~/"))) {
|
|
2855
|
+
identity = resolve3(path.startsWith("~/") ? resolve3(homedir(), path.slice(2)) : path);
|
|
2856
|
+
try {
|
|
2857
|
+
identity = realpathSync(identity);
|
|
2858
|
+
} catch {
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
return `sha256:${createHash2("sha256").update(identity).digest("hex")}`;
|
|
2862
|
+
}
|
|
2863
|
+
function learningArtifactDigest(proposal) {
|
|
2864
|
+
return `sha256:${createHash2("sha256").update(proposal).digest("hex")}`;
|
|
2865
|
+
}
|
|
2866
|
+
function fail(message, status = 400) {
|
|
2867
|
+
throw new LearningStoreError(message, status);
|
|
2868
|
+
}
|
|
2869
|
+
function bounded(value, name, max = 256) {
|
|
2870
|
+
if (typeof value !== "string" || !value.trim() || value.length > max || /\0/.test(value))
|
|
2871
|
+
fail(`Invalid ${name}.`);
|
|
2872
|
+
if (/-----BEGIN .*PRIVATE KEY|(?:api[_-]?key|authorization|password|access[_-]?token)\s*[:=]\s*\S+|\b(?:sk-|ghp_)[A-Za-z0-9_-]{16,}/i.test(value))
|
|
2873
|
+
fail(`Credential-like content in ${name}.`);
|
|
2874
|
+
}
|
|
2875
|
+
function refs(values, name) {
|
|
2876
|
+
if (!Array.isArray(values) || values.length > 64)
|
|
2877
|
+
fail(`Invalid ${name}.`);
|
|
2878
|
+
for (const value of values)
|
|
2879
|
+
bounded(value, name, 1024);
|
|
2880
|
+
}
|
|
2881
|
+
function sources(value) {
|
|
2882
|
+
if (!Array.isArray(value) || value.length > 64)
|
|
2883
|
+
fail("Invalid source links.");
|
|
2884
|
+
for (const item of value) {
|
|
2885
|
+
if (!item || typeof item !== "object" || Object.keys(item).some((key) => !["path", "hash"].includes(key)))
|
|
2886
|
+
fail("Invalid source link.");
|
|
2887
|
+
bounded(item.path, "source path", 4096);
|
|
2888
|
+
bounded(item.hash, "source hash");
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
function limit(value) {
|
|
2892
|
+
return Number.isInteger(value) && value > 0 ? Math.min(value, 200) : 100;
|
|
2893
|
+
}
|
|
2894
|
+
function read(db, table, id) {
|
|
2895
|
+
const row = db.prepare(`SELECT body FROM ${table} WHERE id = ?`).all(id)[0];
|
|
2896
|
+
return row ? JSON.parse(row.body) : null;
|
|
2897
|
+
}
|
|
2898
|
+
function write(db, table, value) {
|
|
2899
|
+
db.prepare(`INSERT INTO ${table}(id,project_id,body,updated_at) VALUES(?,?,?,?) ON CONFLICT(id) DO UPDATE SET body=excluded.body,updated_at=excluded.updated_at`).run(value.id, value.projectId, JSON.stringify(value), value.updatedAt ?? value.createdAt);
|
|
2900
|
+
}
|
|
2901
|
+
function initializeLearningTables(db) {
|
|
2902
|
+
db.exec(`CREATE TABLE IF NOT EXISTS learning_events(id TEXT PRIMARY KEY,idempotency_key TEXT NOT NULL UNIQUE,project_id TEXT NOT NULL,body TEXT NOT NULL,updated_at TEXT NOT NULL);
|
|
2903
|
+
CREATE TABLE IF NOT EXISTS learning_knowledge(id TEXT PRIMARY KEY,project_id TEXT NOT NULL,body TEXT NOT NULL,updated_at TEXT NOT NULL);
|
|
2904
|
+
CREATE TABLE IF NOT EXISTS learning_candidates(id TEXT PRIMARY KEY,project_id TEXT NOT NULL,body TEXT NOT NULL,updated_at TEXT NOT NULL);
|
|
2905
|
+
CREATE TABLE IF NOT EXISTS learning_suggestion_keys(key TEXT PRIMARY KEY,candidate_id TEXT NOT NULL);
|
|
2906
|
+
CREATE TABLE IF NOT EXISTS learning_active(project_id TEXT NOT NULL,kind TEXT NOT NULL,target_id TEXT NOT NULL,candidate_id TEXT NOT NULL,PRIMARY KEY(project_id,kind,target_id));
|
|
2907
|
+
CREATE TABLE IF NOT EXISTS learning_work(id TEXT PRIMARY KEY,event_id TEXT NOT NULL,kind TEXT NOT NULL,status TEXT NOT NULL,attempts INTEGER NOT NULL DEFAULT 0,available_at INTEGER NOT NULL,lease_until INTEGER,claim_token TEXT,owner TEXT,UNIQUE(event_id,kind));
|
|
2908
|
+
CREATE INDEX IF NOT EXISTS learning_events_project ON learning_events(project_id,updated_at);
|
|
2909
|
+
CREATE INDEX IF NOT EXISTS learning_work_ready ON learning_work(status,available_at,lease_until);`);
|
|
2910
|
+
}
|
|
2911
|
+
function appendLearningEvent(db, input) {
|
|
2912
|
+
const allowed = ["idempotencyKey", "projectId", "kind", "outcome", "summary", "evidenceStrength", "workflowId", "workflowRevision", "executionId", "runId", "stepId", "sourceLinks", "evidenceRefs", "evidenceDigests"];
|
|
2913
|
+
if (!input || typeof input !== "object" || Object.keys(input).some((key) => !allowed.includes(key)))
|
|
2914
|
+
fail("Invalid learning event fields.");
|
|
2915
|
+
for (const field of ["idempotencyKey", "projectId", "kind", "outcome"])
|
|
2916
|
+
bounded(input[field], field);
|
|
2917
|
+
bounded(input.summary, "summary", 4e3);
|
|
2918
|
+
for (const field of ["workflowId", "executionId", "runId", "stepId"])
|
|
2919
|
+
if (input[field] !== void 0)
|
|
2920
|
+
bounded(input[field], field);
|
|
2921
|
+
if (input.workflowRevision !== void 0 && (!Number.isSafeInteger(input.workflowRevision) || input.workflowRevision < 1))
|
|
2922
|
+
fail("Invalid workflow revision.");
|
|
2923
|
+
if (input.evidenceStrength !== void 0 && !["operational", "hypothesis", "verified", "human-feedback"].includes(input.evidenceStrength))
|
|
2924
|
+
fail("Invalid evidence strength.");
|
|
2925
|
+
if (input.sourceLinks !== void 0)
|
|
2926
|
+
sources(input.sourceLinks);
|
|
2927
|
+
if (input.evidenceRefs !== void 0)
|
|
2928
|
+
refs(input.evidenceRefs, "evidence refs");
|
|
2929
|
+
if (input.evidenceDigests !== void 0)
|
|
2930
|
+
refs(input.evidenceDigests, "evidence digests");
|
|
2931
|
+
const existing = db.prepare("SELECT body FROM learning_events WHERE idempotency_key=?").all(input.idempotencyKey)[0];
|
|
2932
|
+
if (existing) {
|
|
2933
|
+
const event2 = JSON.parse(existing.body);
|
|
2934
|
+
const { id: _id, createdAt: _createdAt, ...original } = event2;
|
|
2935
|
+
const normalize = (v) => JSON.stringify(Object.fromEntries(Object.entries(v).filter(([, x]) => x !== void 0).sort(([a], [b]) => a.localeCompare(b))));
|
|
2936
|
+
if (normalize(original) !== normalize(input))
|
|
2937
|
+
fail("Idempotency key already describes a different event.", 409);
|
|
2938
|
+
return event2;
|
|
2939
|
+
}
|
|
2940
|
+
const event = { ...input, id: randomUUID3(), createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2941
|
+
db.prepare("INSERT INTO learning_events(id,idempotency_key,project_id,body,updated_at) VALUES(?,?,?,?,?)").run(event.id, event.idempotencyKey, event.projectId, JSON.stringify(event), event.createdAt);
|
|
2942
|
+
return event;
|
|
2943
|
+
}
|
|
2944
|
+
function enqueueLearningWork(db, eventId, kind = "derive-knowledge") {
|
|
2945
|
+
bounded(eventId, "event ID");
|
|
2946
|
+
bounded(kind, "work kind");
|
|
2947
|
+
if (!read(db, "learning_events", eventId))
|
|
2948
|
+
fail("Learning event not found.", 404);
|
|
2949
|
+
db.prepare("INSERT OR IGNORE INTO learning_work(id,event_id,kind,status,available_at) VALUES(?,?,?,'pending',?)").run(randomUUID3(), eventId, kind, Date.now());
|
|
2950
|
+
return workRow(db.prepare("SELECT * FROM learning_work WHERE event_id=? AND kind=?").all(eventId, kind)[0]);
|
|
2951
|
+
}
|
|
2952
|
+
function workRow(row) {
|
|
2953
|
+
return { id: String(row.id), eventId: String(row.event_id), kind: String(row.kind), status: row.status, attempts: Number(row.attempts), availableAt: Number(row.available_at), leaseUntil: row.lease_until === null ? null : Number(row.lease_until), claimToken: row.claim_token === null ? null : String(row.claim_token), owner: row.owner === null ? null : String(row.owner) };
|
|
2954
|
+
}
|
|
2955
|
+
var LearningStore = class {
|
|
2956
|
+
#db;
|
|
2957
|
+
#ownsDatabase;
|
|
2958
|
+
constructor(dbPath) {
|
|
2959
|
+
this.#ownsDatabase = typeof dbPath !== "object";
|
|
2960
|
+
if (typeof dbPath === "object")
|
|
2961
|
+
this.#db = dbPath;
|
|
2962
|
+
else {
|
|
2963
|
+
const path = resolveOsecDbPath(dbPath);
|
|
2964
|
+
ensureDatabaseDirectory(path);
|
|
2965
|
+
this.#db = createShimmedDatabase(path);
|
|
2966
|
+
}
|
|
2967
|
+
this.#db.pragma("busy_timeout = 5000");
|
|
2968
|
+
initializeLearningTables(this.#db);
|
|
2969
|
+
}
|
|
2970
|
+
close() {
|
|
2971
|
+
if (this.#ownsDatabase)
|
|
2972
|
+
this.#db.close();
|
|
2973
|
+
}
|
|
2974
|
+
appendEvent(input) {
|
|
2975
|
+
return this.#db.transaction(() => appendLearningEvent(this.#db, input))();
|
|
2976
|
+
}
|
|
2977
|
+
recordExperience(input, kind = "derive-knowledge") {
|
|
2978
|
+
return this.#db.transaction(() => {
|
|
2979
|
+
const event = appendLearningEvent(this.#db, input);
|
|
2980
|
+
enqueueLearningWork(this.#db, event.id, kind);
|
|
2981
|
+
return event;
|
|
2982
|
+
})();
|
|
2983
|
+
}
|
|
2984
|
+
getEvent(id) {
|
|
2985
|
+
return read(this.#db, "learning_events", id);
|
|
2986
|
+
}
|
|
2987
|
+
#list(table, options = {}) {
|
|
2988
|
+
return this.#db.prepare(`SELECT body FROM ${table} WHERE (? IS NULL OR project_id=?) ORDER BY updated_at DESC,id LIMIT ?`).all(options.projectId ?? null, options.projectId ?? null, limit(options.limit)).map((row) => JSON.parse(row.body));
|
|
2989
|
+
}
|
|
2990
|
+
listEvents(options) {
|
|
2991
|
+
return this.#list("learning_events", options);
|
|
2992
|
+
}
|
|
2993
|
+
listKnowledge(options) {
|
|
2994
|
+
return this.#list("learning_knowledge", options);
|
|
2995
|
+
}
|
|
2996
|
+
getKnowledge(id) {
|
|
2997
|
+
return read(this.#db, "learning_knowledge", id);
|
|
2998
|
+
}
|
|
2999
|
+
#checkEvidence(projectId, ids) {
|
|
3000
|
+
refs(ids, "evidence event IDs");
|
|
3001
|
+
if (!ids.length)
|
|
3002
|
+
fail("Evidence is required.");
|
|
3003
|
+
for (const id of ids) {
|
|
3004
|
+
const event = this.getEvent(id);
|
|
3005
|
+
if (!event || event.projectId !== projectId)
|
|
3006
|
+
fail("Evidence must belong to the same project.");
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
putKnowledge(input) {
|
|
3010
|
+
bounded(input.projectId, "project ID");
|
|
3011
|
+
bounded(input.summary, "knowledge summary", 4e3);
|
|
3012
|
+
sources(input.sourceLinks);
|
|
3013
|
+
this.#checkEvidence(input.projectId, input.evidenceEventIds);
|
|
3014
|
+
if (input.sourceLinks.length) {
|
|
3015
|
+
const fingerprint = (links) => JSON.stringify([...links].sort((a, b) => a.path.localeCompare(b.path) || a.hash.localeCompare(b.hash)));
|
|
3016
|
+
const matching = this.#db.prepare("SELECT body FROM learning_knowledge WHERE project_id=? AND json_extract(body,'$.summary')=? AND json_extract(body,'$.status')='disabled'").all(input.projectId, input.summary);
|
|
3017
|
+
for (const row of matching) {
|
|
3018
|
+
const prior = JSON.parse(row.body);
|
|
3019
|
+
if (fingerprint(prior.sourceLinks) === fingerprint(input.sourceLinks))
|
|
3020
|
+
return prior;
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
const identity = `knowledge:${createHash2("sha256").update(JSON.stringify({ projectId: input.projectId, summary: input.summary, sourceLinks: [...input.sourceLinks].sort((a, b) => a.path.localeCompare(b.path)), evidenceEventIds: [...input.evidenceEventIds].sort() })).digest("hex")}`;
|
|
3024
|
+
const existing = this.getKnowledge(identity);
|
|
3025
|
+
if (existing)
|
|
3026
|
+
return existing;
|
|
3027
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3028
|
+
const entry = { projectId: input.projectId, summary: input.summary, sourceLinks: input.sourceLinks, evidenceEventIds: input.evidenceEventIds, id: identity, status: "current", createdAt: now, updatedAt: now };
|
|
3029
|
+
write(this.#db, "learning_knowledge", entry);
|
|
3030
|
+
return entry;
|
|
3031
|
+
}
|
|
3032
|
+
setKnowledgeStatus(id, status) {
|
|
3033
|
+
if (!["current", "stale", "disabled"].includes(status))
|
|
3034
|
+
fail("Invalid knowledge status.");
|
|
3035
|
+
const entry = this.getKnowledge(id);
|
|
3036
|
+
if (!entry)
|
|
3037
|
+
fail("Knowledge not found.", 404);
|
|
3038
|
+
if (status === "current" && entry.status !== "current")
|
|
3039
|
+
fail("Retain fresh evidence before reactivating knowledge.", 409);
|
|
3040
|
+
entry.status = status;
|
|
3041
|
+
entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3042
|
+
write(this.#db, "learning_knowledge", entry);
|
|
3043
|
+
return entry;
|
|
3044
|
+
}
|
|
3045
|
+
invalidateKnowledge(projectId, currentHashes) {
|
|
3046
|
+
bounded(projectId, "project ID");
|
|
3047
|
+
return this.#db.transaction(() => {
|
|
3048
|
+
let count = 0;
|
|
3049
|
+
const rows = this.#db.prepare("SELECT body FROM learning_knowledge WHERE project_id=?").all(projectId);
|
|
3050
|
+
for (const row of rows) {
|
|
3051
|
+
const entry = JSON.parse(row.body);
|
|
3052
|
+
if (entry.status === "current" && entry.sourceLinks.some((link) => !Object.hasOwn(currentHashes, link.path) || currentHashes[link.path] !== link.hash)) {
|
|
3053
|
+
this.setKnowledgeStatus(entry.id, "stale");
|
|
3054
|
+
count++;
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
return count;
|
|
3058
|
+
})();
|
|
3059
|
+
}
|
|
3060
|
+
createCandidate(input) {
|
|
3061
|
+
for (const field of ["projectId", "kind", "targetId", "baseVersion"])
|
|
3062
|
+
bounded(input[field], field);
|
|
3063
|
+
bounded(input.proposal, "proposal", 16e3);
|
|
3064
|
+
this.#checkEvidence(input.projectId, input.evidenceEventIds);
|
|
3065
|
+
if (input.artifactDigest !== void 0 && input.artifactDigest !== learningArtifactDigest(input.proposal))
|
|
3066
|
+
fail("Candidate artifact digest mismatch.");
|
|
3067
|
+
if (input.registry) {
|
|
3068
|
+
for (const field of ["registryVersionId", "registryStorePath", "snapshotDigest"])
|
|
3069
|
+
bounded(input.registry[field], field, 4096);
|
|
3070
|
+
if (!["candidate", "canary", "active", "retired"].includes(input.registry.registryStatus))
|
|
3071
|
+
fail("Invalid registry status.");
|
|
3072
|
+
}
|
|
3073
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3074
|
+
const value = { projectId: input.projectId, kind: input.kind, targetId: input.targetId, baseVersion: input.baseVersion, proposal: input.proposal, evidenceEventIds: input.evidenceEventIds, artifactDigest: learningArtifactDigest(input.proposal), ...input.registry ? { registry: input.registry } : {}, id: randomUUID3(), status: "proposed", revision: 1, evaluations: [], createdAt: now, updatedAt: now };
|
|
3075
|
+
write(this.#db, "learning_candidates", value);
|
|
3076
|
+
return value;
|
|
3077
|
+
}
|
|
3078
|
+
createWorkflowSuggestion(input) {
|
|
3079
|
+
if (input.kind !== "workflow-restore" || input.registry)
|
|
3080
|
+
fail("Invalid workflow suggestion.");
|
|
3081
|
+
const key = learningArtifactDigest(JSON.stringify([input.projectId, input.targetId, input.baseVersion, input.proposal]));
|
|
3082
|
+
return this.#db.transaction(() => {
|
|
3083
|
+
if (this.#db.prepare("SELECT candidate_id FROM learning_suggestion_keys WHERE key=?").all(key).length)
|
|
3084
|
+
return null;
|
|
3085
|
+
const candidate = this.createCandidate(input);
|
|
3086
|
+
this.#db.prepare("INSERT INTO learning_suggestion_keys(key,candidate_id) VALUES(?,?)").run(key, candidate.id);
|
|
3087
|
+
return candidate;
|
|
3088
|
+
})();
|
|
3089
|
+
}
|
|
3090
|
+
getCandidate(id) {
|
|
3091
|
+
return read(this.#db, "learning_candidates", id);
|
|
3092
|
+
}
|
|
3093
|
+
listCandidates(options) {
|
|
3094
|
+
return this.#list("learning_candidates", options);
|
|
3095
|
+
}
|
|
3096
|
+
#candidate(id, revision) {
|
|
3097
|
+
const value = this.getCandidate(id);
|
|
3098
|
+
if (!value)
|
|
3099
|
+
fail("Candidate not found.", 404);
|
|
3100
|
+
if (value.revision !== revision)
|
|
3101
|
+
fail("Candidate changed. Reload before updating.", 409);
|
|
3102
|
+
return value;
|
|
3103
|
+
}
|
|
3104
|
+
#saveCandidate(value) {
|
|
3105
|
+
value.revision++;
|
|
3106
|
+
value.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3107
|
+
write(this.#db, "learning_candidates", value);
|
|
3108
|
+
return value;
|
|
3109
|
+
}
|
|
3110
|
+
transitionCandidate(id, revision, status) {
|
|
3111
|
+
return this.#db.transaction(() => {
|
|
3112
|
+
const value = this.#candidate(id, revision);
|
|
3113
|
+
const allowed = { proposed: ["evaluating", "rejected"], evaluating: ["rejected"], validated: ["rejected"] };
|
|
3114
|
+
if (!allowed[value.status]?.includes(status))
|
|
3115
|
+
fail("Invalid candidate transition.", 409);
|
|
3116
|
+
value.status = status;
|
|
3117
|
+
return this.#saveCandidate(value);
|
|
3118
|
+
})();
|
|
3119
|
+
}
|
|
3120
|
+
recordEvaluation(id, revision, input) {
|
|
3121
|
+
for (const field of ["suiteDigest", "baselineDigest", "candidateDigest"])
|
|
3122
|
+
bounded(input[field], field);
|
|
3123
|
+
if (input.evaluatorDigest !== void 0)
|
|
3124
|
+
bounded(input.evaluatorDigest, "evaluator digest");
|
|
3125
|
+
if (input.evaluationKind !== void 0 && !["output-fixture", "security-proof"].includes(input.evaluationKind))
|
|
3126
|
+
fail("Invalid evaluation kind.");
|
|
3127
|
+
if (input.outcome !== void 0 && !["passed", "failed", "inconclusive"].includes(input.outcome))
|
|
3128
|
+
fail("Invalid evaluation outcome.");
|
|
3129
|
+
if (input.outcome !== void 0 && input.passed !== (input.outcome === "passed"))
|
|
3130
|
+
fail("Contradictory evaluation outcome.");
|
|
3131
|
+
if (typeof input.passed !== "boolean" || !input.metrics || typeof input.metrics !== "object" || Array.isArray(input.metrics) || Object.keys(input.metrics).length > 32 || Object.entries(input.metrics).some(([key, value]) => key.length > 128 || typeof value !== "number" || !Number.isFinite(value)))
|
|
3132
|
+
fail("Invalid evaluation metrics.");
|
|
3133
|
+
return this.#db.transaction(() => {
|
|
3134
|
+
const value = this.#candidate(id, revision);
|
|
3135
|
+
if (value.evaluations.length >= 64)
|
|
3136
|
+
fail("Too many evaluation receipts.");
|
|
3137
|
+
if (value.status !== "evaluating")
|
|
3138
|
+
fail("Candidate is not evaluating.", 409);
|
|
3139
|
+
if (input.candidateDigest !== value.artifactDigest)
|
|
3140
|
+
fail("Evaluation does not match the candidate artifact.", 409);
|
|
3141
|
+
value.evaluations.push({ ...input.evaluatorDigest ? { evaluatorDigest: input.evaluatorDigest } : {}, ...input.evaluationKind ? { evaluationKind: input.evaluationKind } : {}, ...input.outcome ? { outcome: input.outcome } : {}, suiteDigest: input.suiteDigest, baselineDigest: input.baselineDigest, candidateDigest: input.candidateDigest, passed: input.passed, metrics: input.metrics, id: randomUUID3(), createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3142
|
+
value.status = input.outcome === "inconclusive" ? "evaluating" : input.passed ? "validated" : "rejected";
|
|
3143
|
+
return this.#saveCandidate(value);
|
|
3144
|
+
})();
|
|
3145
|
+
}
|
|
3146
|
+
/** Reviewed workflow edit, distinct from evaluated security deployment. */
|
|
3147
|
+
markWorkflowSuggestionApplied(id, revision) {
|
|
3148
|
+
const value = this.#candidate(id, revision);
|
|
3149
|
+
if (value.kind !== "workflow-restore" || value.registry || value.status !== "proposed")
|
|
3150
|
+
fail("Suggestion is no longer available.", 409);
|
|
3151
|
+
value.status = "applied";
|
|
3152
|
+
return this.#saveCandidate(value);
|
|
3153
|
+
}
|
|
3154
|
+
getActiveCandidate(projectId, kind, targetId) {
|
|
3155
|
+
const row = this.#db.prepare("SELECT candidate_id FROM learning_active WHERE project_id=? AND kind=? AND target_id=?").all(projectId, kind, targetId)[0];
|
|
3156
|
+
return row ? this.getCandidate(row.candidate_id) : null;
|
|
3157
|
+
}
|
|
3158
|
+
activateCandidate(id, revision, expectedActiveId = null) {
|
|
3159
|
+
return this.#db.transaction(() => {
|
|
3160
|
+
const value = this.#candidate(id, revision);
|
|
3161
|
+
if (value.status !== "validated" || !value.evaluations.at(-1)?.passed)
|
|
3162
|
+
fail("Candidate needs a passing evaluation.", 409);
|
|
3163
|
+
const active = this.getActiveCandidate(value.projectId, value.kind, value.targetId);
|
|
3164
|
+
if ((active?.id ?? null) !== expectedActiveId)
|
|
3165
|
+
fail("Active version changed.", 409);
|
|
3166
|
+
if (active) {
|
|
3167
|
+
active.status = "retired";
|
|
3168
|
+
this.#saveCandidate(active);
|
|
3169
|
+
value.previousActiveId = active.id;
|
|
3170
|
+
}
|
|
3171
|
+
value.status = "active";
|
|
3172
|
+
this.#db.prepare("INSERT INTO learning_active(project_id,kind,target_id,candidate_id) VALUES(?,?,?,?) ON CONFLICT(project_id,kind,target_id) DO UPDATE SET candidate_id=excluded.candidate_id").run(value.projectId, value.kind, value.targetId, value.id);
|
|
3173
|
+
return this.#saveCandidate(value);
|
|
3174
|
+
})();
|
|
3175
|
+
}
|
|
3176
|
+
rollbackCandidate(id, revision) {
|
|
3177
|
+
return this.#db.transaction(() => {
|
|
3178
|
+
const value = this.#candidate(id, revision);
|
|
3179
|
+
if (value.status !== "active" || this.getActiveCandidate(value.projectId, value.kind, value.targetId)?.id !== id)
|
|
3180
|
+
fail("Candidate is not active.", 409);
|
|
3181
|
+
value.status = "retired";
|
|
3182
|
+
const previous = value.previousActiveId ? this.getCandidate(value.previousActiveId) : null;
|
|
3183
|
+
if (previous) {
|
|
3184
|
+
previous.status = "active";
|
|
3185
|
+
this.#saveCandidate(previous);
|
|
3186
|
+
this.#db.prepare("UPDATE learning_active SET candidate_id=? WHERE project_id=? AND kind=? AND target_id=?").run(previous.id, value.projectId, value.kind, value.targetId);
|
|
3187
|
+
} else
|
|
3188
|
+
this.#db.prepare("DELETE FROM learning_active WHERE project_id=? AND kind=? AND target_id=?").run(value.projectId, value.kind, value.targetId);
|
|
3189
|
+
return this.#saveCandidate(value);
|
|
3190
|
+
})();
|
|
3191
|
+
}
|
|
3192
|
+
enqueue(eventId, kind = "derive-knowledge") {
|
|
3193
|
+
return enqueueLearningWork(this.#db, eventId, kind);
|
|
3194
|
+
}
|
|
3195
|
+
claimWork(owner, leaseMs = 6e4, now = Date.now(), projectId) {
|
|
3196
|
+
bounded(owner, "worker owner");
|
|
3197
|
+
if (!Number.isSafeInteger(now) || !Number.isSafeInteger(leaseMs) || leaseMs < 100 || leaseMs > 36e5)
|
|
3198
|
+
fail("Invalid lease.");
|
|
3199
|
+
return this.#db.transaction(() => {
|
|
3200
|
+
const row = this.#db.prepare("SELECT id FROM learning_work WHERE ((status='pending' AND available_at<=?) OR (status='claimed' AND lease_until<=?)) AND (? IS NULL OR event_id IN (SELECT id FROM learning_events WHERE project_id=?)) ORDER BY available_at,id LIMIT 1").all(now, now, projectId ?? null, projectId ?? null)[0];
|
|
3201
|
+
if (!row)
|
|
3202
|
+
return null;
|
|
3203
|
+
this.#db.prepare("UPDATE learning_work SET status='claimed',owner=?,claim_token=?,lease_until=?,attempts=attempts+1 WHERE id=?").run(owner, randomUUID3(), now + leaseMs, row.id);
|
|
3204
|
+
return workRow(this.#db.prepare("SELECT * FROM learning_work WHERE id=?").all(row.id)[0]);
|
|
3205
|
+
})();
|
|
3206
|
+
}
|
|
3207
|
+
queueStatus(projectId) {
|
|
3208
|
+
const counts = { pending: 0, claimed: 0, completed: 0 };
|
|
3209
|
+
for (const row of this.#db.prepare("SELECT status,COUNT(*) AS count FROM learning_work WHERE (? IS NULL OR event_id IN (SELECT id FROM learning_events WHERE project_id=?)) GROUP BY status").all(projectId ?? null, projectId ?? null)) {
|
|
3210
|
+
const value = row;
|
|
3211
|
+
counts[value.status] = Number(value.count);
|
|
3212
|
+
}
|
|
3213
|
+
return counts;
|
|
3214
|
+
}
|
|
3215
|
+
/** Mirror a trusted external registry's existing lifecycle; this is not a promotion API. */
|
|
3216
|
+
mirrorRegistryCandidate(input, status, evaluations = []) {
|
|
3217
|
+
if (!["proposed", "canary", "active", "retired", "rejected", "validated", "evaluating"].includes(status))
|
|
3218
|
+
fail("Invalid registry mirror status.");
|
|
3219
|
+
const matching = { candidate: status === "rejected" ? "rejected" : "proposed", canary: "canary", active: "active", retired: "retired" };
|
|
3220
|
+
if (matching[input.registry.registryStatus] !== status)
|
|
3221
|
+
fail("Mirror status must match the registry.");
|
|
3222
|
+
if (evaluations.length > 64)
|
|
3223
|
+
fail("Too many evaluation receipts.");
|
|
3224
|
+
for (const receipt of evaluations) {
|
|
3225
|
+
if (receipt.evaluationKind !== "output-fixture")
|
|
3226
|
+
fail("External registry receipts are output fixtures.");
|
|
3227
|
+
for (const field of ["suiteDigest", "baselineDigest", "candidateDigest"])
|
|
3228
|
+
bounded(receipt[field], field);
|
|
3229
|
+
if (receipt.candidateDigest !== input.registry.snapshotDigest)
|
|
3230
|
+
fail("Registry receipt snapshot does not match.");
|
|
3231
|
+
if (typeof receipt.passed !== "boolean" || !receipt.metrics || typeof receipt.metrics !== "object" || Array.isArray(receipt.metrics) || Object.keys(receipt.metrics).length > 32 || Object.entries(receipt.metrics).some(([k, v]) => k.length > 128 || typeof v !== "number" || !Number.isFinite(v)))
|
|
3232
|
+
fail("Invalid mirror receipt.");
|
|
3233
|
+
}
|
|
3234
|
+
return this.#db.transaction(() => {
|
|
3235
|
+
const rows = this.#db.prepare("SELECT body FROM learning_candidates WHERE project_id=?").all(input.projectId);
|
|
3236
|
+
const existing = rows.map((row) => JSON.parse(row.body)).find((candidate) => candidate.registry?.registryStorePath === input.registry.registryStorePath && candidate.registry.registryVersionId === input.registry.registryVersionId);
|
|
3237
|
+
if (existing && (existing.proposal !== input.proposal || existing.registry?.snapshotDigest !== input.registry.snapshotDigest))
|
|
3238
|
+
fail("Immutable registry artifact changed.", 409);
|
|
3239
|
+
const value = existing ?? this.createCandidate(input);
|
|
3240
|
+
value.status = status;
|
|
3241
|
+
value.registry = input.registry;
|
|
3242
|
+
for (const receipt of evaluations)
|
|
3243
|
+
if (!value.evaluations.some((v) => v.suiteDigest === receipt.suiteDigest && v.candidateDigest === receipt.candidateDigest && v.baselineDigest === receipt.baselineDigest && v.passed === receipt.passed))
|
|
3244
|
+
value.evaluations.push({ ...receipt, id: randomUUID3(), createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3245
|
+
return this.#saveCandidate(value);
|
|
3246
|
+
})();
|
|
3247
|
+
}
|
|
3248
|
+
#finishWork(id, token, status, delayMs, now) {
|
|
3249
|
+
if (!Number.isSafeInteger(delayMs) || delayMs < 0 || delayMs > 864e5 || !Number.isSafeInteger(now))
|
|
3250
|
+
fail("Invalid retry delay.");
|
|
3251
|
+
const result = this.#db.prepare("UPDATE learning_work SET status=?,available_at=?,owner=NULL,claim_token=NULL,lease_until=NULL WHERE id=? AND status='claimed' AND claim_token=? AND lease_until>?").run(status, now + delayMs, id, token, now);
|
|
3252
|
+
if (!result.changes)
|
|
3253
|
+
fail("Work lease expired or belongs to another worker.", 409);
|
|
3254
|
+
}
|
|
3255
|
+
completeWork(id, token, now = Date.now()) {
|
|
3256
|
+
this.#finishWork(id, token, "completed", 0, now);
|
|
3257
|
+
}
|
|
3258
|
+
retryWork(id, token, delayMs = 1e3, now = Date.now()) {
|
|
3259
|
+
this.#finishWork(id, token, "pending", delayMs, now);
|
|
3260
|
+
}
|
|
3261
|
+
};
|
|
3262
|
+
|
|
3263
|
+
// packages/db/dist/security-workflows.js
|
|
3264
|
+
function workflowRestorePolicy(workflow) {
|
|
3265
|
+
return JSON.stringify({
|
|
3266
|
+
target: workflow.target,
|
|
3267
|
+
template: workflow.template,
|
|
3268
|
+
edges: workflow.edges,
|
|
3269
|
+
nodes: workflow.nodes.map(({ label: _label, execution, ...node }) => ({
|
|
3270
|
+
...node,
|
|
3271
|
+
...execution ? { execution: { allowedAgentTools: execution.allowedAgentTools } } : {}
|
|
3272
|
+
}))
|
|
3273
|
+
});
|
|
3274
|
+
}
|
|
2620
3275
|
var SecurityWorkflowStoreError = class extends Error {
|
|
2621
3276
|
statusCode;
|
|
2622
3277
|
constructor(message, statusCode) {
|
|
@@ -2629,17 +3284,39 @@ var activeStatuses = { queued: true, running: true };
|
|
|
2629
3284
|
var statuses = { queued: true, running: true, completed: true, failed: true, cancelled: true, interrupted: true };
|
|
2630
3285
|
var SecurityWorkflowStore = class {
|
|
2631
3286
|
#db;
|
|
2632
|
-
#runnerInstanceId =
|
|
3287
|
+
#runnerInstanceId = randomUUID4();
|
|
2633
3288
|
constructor(dbPath) {
|
|
2634
3289
|
const path = resolveOsecDbPath(dbPath);
|
|
2635
3290
|
ensureDatabaseDirectory(path);
|
|
2636
3291
|
this.#db = createShimmedDatabase(path);
|
|
2637
3292
|
this.#db.pragma("busy_timeout = 5000");
|
|
3293
|
+
initializeLearningTables(this.#db);
|
|
2638
3294
|
this.#db.exec(`CREATE TABLE IF NOT EXISTS workflow_definitions (id TEXT PRIMARY KEY, revision INTEGER NOT NULL, definition_json TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
|
|
2639
3295
|
CREATE TABLE IF NOT EXISTS workflow_executions (id TEXT PRIMARY KEY, workflow_id TEXT NOT NULL, workflow_revision INTEGER NOT NULL, session_id TEXT NOT NULL, status TEXT NOT NULL, execution_json TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
|
|
2640
3296
|
CREATE INDEX IF NOT EXISTS workflow_executions_workflow ON workflow_executions(workflow_id, created_at);
|
|
2641
3297
|
CREATE INDEX IF NOT EXISTS workflow_executions_session ON workflow_executions(session_id, created_at);`);
|
|
2642
3298
|
this.#db.exec("CREATE TABLE IF NOT EXISTS workflow_execution_results (execution_id TEXT PRIMARY KEY, result_json TEXT NOT NULL);");
|
|
3299
|
+
this.#db.exec(`CREATE TABLE IF NOT EXISTS workflow_definition_versions (
|
|
3300
|
+
workflow_id TEXT NOT NULL, revision INTEGER NOT NULL, definition_json TEXT NOT NULL,
|
|
3301
|
+
parent_revision INTEGER, restored_from_revision INTEGER, digest TEXT NOT NULL, created_at TEXT NOT NULL,
|
|
3302
|
+
PRIMARY KEY (workflow_id, revision));`);
|
|
3303
|
+
this.#db.transaction(() => {
|
|
3304
|
+
for (const workflow of this.list()) {
|
|
3305
|
+
if (this.getVersion(workflow.id, workflow.revision))
|
|
3306
|
+
continue;
|
|
3307
|
+
this.#recordVersion(workflow, null, null, true);
|
|
3308
|
+
const snapshots = this.#db.prepare("SELECT execution_json FROM workflow_executions WHERE workflow_id = ? ORDER BY created_at, id").all(workflow.id);
|
|
3309
|
+
for (const row of snapshots) {
|
|
3310
|
+
const execution = JSON.parse(String(row.execution_json));
|
|
3311
|
+
if (execution.workflow.revision < workflow.revision)
|
|
3312
|
+
this.#recordVersion(execution.workflow, null, null, true);
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
})();
|
|
3316
|
+
}
|
|
3317
|
+
/** Shares the control connection so learning and workflow transactions cannot race. */
|
|
3318
|
+
learningStore() {
|
|
3319
|
+
return new LearningStore(this.#db);
|
|
2643
3320
|
}
|
|
2644
3321
|
list() {
|
|
2645
3322
|
return this.#db.prepare("SELECT definition_json FROM workflow_definitions ORDER BY updated_at DESC, id").all().map((row) => JSON.parse(String(row.definition_json)));
|
|
@@ -2648,6 +3325,65 @@ var SecurityWorkflowStore = class {
|
|
|
2648
3325
|
const row = this.#db.prepare("SELECT definition_json FROM workflow_definitions WHERE id = ?").all(id)[0];
|
|
2649
3326
|
return row ? JSON.parse(String(row.definition_json)) : null;
|
|
2650
3327
|
}
|
|
3328
|
+
listVersions(id) {
|
|
3329
|
+
return this.#db.prepare("SELECT * FROM workflow_definition_versions WHERE workflow_id = ? ORDER BY revision DESC").all(id).map(readVersion);
|
|
3330
|
+
}
|
|
3331
|
+
getVersion(id, revision) {
|
|
3332
|
+
const row = this.#db.prepare("SELECT * FROM workflow_definition_versions WHERE workflow_id = ? AND revision = ?").all(id, revision)[0];
|
|
3333
|
+
return row ? readVersion(row) : null;
|
|
3334
|
+
}
|
|
3335
|
+
#recordVersion(workflow, parentRevision, restoredFromRevision, recover = false) {
|
|
3336
|
+
const json = JSON.stringify(workflow);
|
|
3337
|
+
this.#db.prepare(`${recover ? "INSERT OR IGNORE" : "INSERT"} INTO workflow_definition_versions (workflow_id, revision, definition_json, parent_revision, restored_from_revision, digest, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`).run(workflow.id, workflow.revision, json, parentRevision, restoredFromRevision, createHash3("sha256").update(json).digest("hex"), workflow.updatedAt);
|
|
3338
|
+
}
|
|
3339
|
+
/** CAS restore: existing runs and reviewed schedule revisions remain pinned. */
|
|
3340
|
+
rollback(id, targetRevision, expectedRevision) {
|
|
3341
|
+
if (!Number.isInteger(targetRevision) || targetRevision < 1 || !Number.isInteger(expectedRevision) || expectedRevision < 1)
|
|
3342
|
+
throw new SecurityWorkflowStoreError("Invalid workflow revision.", 400);
|
|
3343
|
+
return this.#db.transaction(() => {
|
|
3344
|
+
const current = this.get(id);
|
|
3345
|
+
if (!current)
|
|
3346
|
+
throw new SecurityWorkflowStoreError("Workflow not found.", 404);
|
|
3347
|
+
if (current.revision !== expectedRevision)
|
|
3348
|
+
throw new SecurityWorkflowStoreError("Workflow changed. Reload before restoring.", 409);
|
|
3349
|
+
const previous = this.getVersion(id, targetRevision);
|
|
3350
|
+
if (!previous)
|
|
3351
|
+
throw new SecurityWorkflowStoreError("Workflow version not found.", 404);
|
|
3352
|
+
const workflow = { ...previous.definition, revision: current.revision + 1, createdAt: current.createdAt, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3353
|
+
this.#db.prepare("UPDATE workflow_definitions SET revision = ?, definition_json = ?, updated_at = ? WHERE id = ? AND revision = ?").run(workflow.revision, JSON.stringify(workflow), workflow.updatedAt, id, expectedRevision);
|
|
3354
|
+
this.#recordVersion(workflow, current.revision, targetRevision);
|
|
3355
|
+
return workflow;
|
|
3356
|
+
})();
|
|
3357
|
+
}
|
|
3358
|
+
applyLearningRestore(candidateId, candidateRevision, expectedWorkflowRevision) {
|
|
3359
|
+
return this.#db.transaction(() => {
|
|
3360
|
+
const learning = this.learningStore();
|
|
3361
|
+
const candidate = learning.getCandidate(candidateId);
|
|
3362
|
+
if (!candidate || candidate.kind !== "workflow-restore" || candidate.status !== "proposed" || candidate.revision !== candidateRevision)
|
|
3363
|
+
throw new SecurityWorkflowStoreError("Suggestion changed. Reload before applying.", 409);
|
|
3364
|
+
const proposal = JSON.parse(candidate.proposal);
|
|
3365
|
+
const current = this.get(candidate.targetId);
|
|
3366
|
+
const previous = this.getVersion(candidate.targetId, proposal.restoreRevision)?.definition;
|
|
3367
|
+
if (!current || !previous || proposal.action !== "restore-workflow-instructions" || proposal.workflowId !== current.id || current.revision !== expectedWorkflowRevision || proposal.fromRevision !== current.revision || candidate.baseVersion !== String(current.revision) || candidate.projectId !== learningProjectId(current.target || current.id) || workflowRestorePolicy(current) !== workflowRestorePolicy(previous))
|
|
3368
|
+
throw new SecurityWorkflowStoreError("Workflow changed or its scope differs. Review it again.", 409);
|
|
3369
|
+
const updated = { ...previous, revision: current.revision + 1, createdAt: current.createdAt, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3370
|
+
this.#db.prepare("UPDATE workflow_definitions SET revision = ?, definition_json = ?, updated_at = ? WHERE id = ? AND revision = ?").run(updated.revision, JSON.stringify(updated), updated.updatedAt, current.id, current.revision);
|
|
3371
|
+
this.#recordVersion(updated, current.revision, previous.revision);
|
|
3372
|
+
learning.markWorkflowSuggestionApplied(candidate.id, candidateRevision);
|
|
3373
|
+
appendLearningEvent(this.#db, {
|
|
3374
|
+
idempotencyKey: `workflow-suggestion-applied:${candidate.id}`,
|
|
3375
|
+
projectId: candidate.projectId,
|
|
3376
|
+
kind: "workflow-suggestion",
|
|
3377
|
+
outcome: "applied",
|
|
3378
|
+
summary: "Earlier workflow instructions restored after review.",
|
|
3379
|
+
evidenceStrength: "operational",
|
|
3380
|
+
workflowId: current.id,
|
|
3381
|
+
workflowRevision: updated.revision,
|
|
3382
|
+
evidenceRefs: [candidate.id]
|
|
3383
|
+
});
|
|
3384
|
+
return updated;
|
|
3385
|
+
})();
|
|
3386
|
+
}
|
|
2651
3387
|
save(value) {
|
|
2652
3388
|
let input;
|
|
2653
3389
|
try {
|
|
@@ -2661,12 +3397,15 @@ var SecurityWorkflowStore = class {
|
|
|
2661
3397
|
throw new SecurityWorkflowStoreError("Workflow not found.", 404);
|
|
2662
3398
|
if (existing && input.revision !== existing.revision)
|
|
2663
3399
|
throw new SecurityWorkflowStoreError("Workflow changed. Reload before saving.", 409);
|
|
3400
|
+
if (input.id && !existing && this.#db.prepare("SELECT 1 FROM workflow_definition_versions WHERE workflow_id = ? LIMIT 1").all(input.id).length > 0)
|
|
3401
|
+
throw new SecurityWorkflowStoreError("Deleted workflow IDs cannot be reused.", 409);
|
|
2664
3402
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2665
|
-
const workflow = { ...input, id: input.id ??
|
|
3403
|
+
const workflow = { ...input, id: input.id ?? randomUUID4(), revision: (existing?.revision ?? 0) + 1, createdAt: existing?.createdAt ?? now, updatedAt: now };
|
|
2666
3404
|
if (existing)
|
|
2667
3405
|
this.#db.prepare("UPDATE workflow_definitions SET revision = ?, definition_json = ?, updated_at = ? WHERE id = ?").run(workflow.revision, JSON.stringify(workflow), now, workflow.id);
|
|
2668
3406
|
else
|
|
2669
3407
|
this.#db.prepare("INSERT INTO workflow_definitions (id, revision, definition_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(workflow.id, workflow.revision, JSON.stringify(workflow), now, now);
|
|
3408
|
+
this.#recordVersion(workflow, existing?.revision ?? null, null);
|
|
2670
3409
|
return workflow;
|
|
2671
3410
|
})();
|
|
2672
3411
|
}
|
|
@@ -2691,7 +3430,7 @@ var SecurityWorkflowStore = class {
|
|
|
2691
3430
|
if (revision !== void 0 && revision !== workflow.revision)
|
|
2692
3431
|
throw new SecurityWorkflowStoreError("Workflow changed. Review it before running.", 409);
|
|
2693
3432
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2694
|
-
const execution = { id:
|
|
3433
|
+
const execution = { id: randomUUID4(), workflowId, workflowRevision: workflow.revision, workflow, sessionId, ownerPid: process.pid, runnerInstanceId: this.#runnerInstanceId, status: "queued", nodeResults: {}, createdAt: now, updatedAt: now };
|
|
2695
3434
|
this.#db.prepare("INSERT INTO workflow_executions (id, workflow_id, workflow_revision, session_id, status, execution_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(execution.id, workflowId, workflow.revision, sessionId, execution.status, JSON.stringify(execution), now, now);
|
|
2696
3435
|
return execution;
|
|
2697
3436
|
})();
|
|
@@ -2702,8 +3441,8 @@ var SecurityWorkflowStore = class {
|
|
|
2702
3441
|
throw new SecurityWorkflowStoreError("Invalid execution owner.", 400);
|
|
2703
3442
|
const input = parseSecurityWorkflowInput(value);
|
|
2704
3443
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2705
|
-
const workflow = { ...input, id: input.id ??
|
|
2706
|
-
const execution = { id:
|
|
3444
|
+
const workflow = { ...input, id: input.id ?? randomUUID4(), revision: input.revision ?? 1, createdAt: now, updatedAt: now };
|
|
3445
|
+
const execution = { id: randomUUID4(), workflowId: workflow.id, workflowRevision: workflow.revision, workflow, sessionId, ownerPid: process.pid, runnerInstanceId: this.#runnerInstanceId, status: "queued", nodeResults: {}, createdAt: now, updatedAt: now };
|
|
2707
3446
|
this.#db.prepare("INSERT INTO workflow_executions (id, workflow_id, workflow_revision, session_id, status, execution_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(execution.id, execution.workflowId, execution.workflowRevision, sessionId, execution.status, JSON.stringify(execution), now, now);
|
|
2708
3447
|
return execution;
|
|
2709
3448
|
}
|
|
@@ -2757,6 +3496,29 @@ var SecurityWorkflowStore = class {
|
|
|
2757
3496
|
}
|
|
2758
3497
|
const execution = { ...current, status: update.status ?? current.status, jobId: update.jobId ?? current.jobId, nodeResults: update.nodeResults ?? current.nodeResults, error: update.error ?? current.error, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2759
3498
|
this.#db.prepare("UPDATE workflow_executions SET status = ?, execution_json = ?, updated_at = ? WHERE id = ?").run(execution.status, JSON.stringify(execution), execution.updatedAt, id);
|
|
3499
|
+
const projectId = learningProjectId(execution.workflow.target || execution.workflowId);
|
|
3500
|
+
const observe = (kind, outcome, stepId) => {
|
|
3501
|
+
const event = appendLearningEvent(this.#db, {
|
|
3502
|
+
idempotencyKey: `workflow-terminal:${createHash3("sha256").update(JSON.stringify({ executionId: id, stepId: stepId ?? null, kind, outcome })).digest("hex")}`,
|
|
3503
|
+
projectId,
|
|
3504
|
+
kind,
|
|
3505
|
+
outcome,
|
|
3506
|
+
evidenceStrength: "operational",
|
|
3507
|
+
summary: stepId ? `Workflow step ${outcome}.` : `Workflow execution ${outcome}.`,
|
|
3508
|
+
executionId: id,
|
|
3509
|
+
workflowId: execution.workflowId,
|
|
3510
|
+
workflowRevision: execution.workflowRevision,
|
|
3511
|
+
...stepId ? { stepId } : {}
|
|
3512
|
+
});
|
|
3513
|
+
enqueueLearningWork(this.#db, event.id);
|
|
3514
|
+
};
|
|
3515
|
+
const terminalNodes = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "interrupted", "skipped", "blocked"]);
|
|
3516
|
+
for (const [stepId, result] of Object.entries(execution.nodeResults)) {
|
|
3517
|
+
if (terminalNodes.has(result.status) && !terminalNodes.has(current.nodeResults[stepId]?.status ?? ""))
|
|
3518
|
+
observe("workflow-step", result.status, stepId);
|
|
3519
|
+
}
|
|
3520
|
+
if (!activeStatuses[execution.status])
|
|
3521
|
+
observe("workflow-run", execution.status);
|
|
2760
3522
|
return execution;
|
|
2761
3523
|
})();
|
|
2762
3524
|
}
|
|
@@ -2789,9 +3551,13 @@ function processAlive(pid) {
|
|
|
2789
3551
|
return !(error && typeof error === "object" && "code" in error && error.code === "ESRCH");
|
|
2790
3552
|
}
|
|
2791
3553
|
}
|
|
3554
|
+
function readVersion(value) {
|
|
3555
|
+
const row = value;
|
|
3556
|
+
return { workflowId: row.workflow_id, revision: row.revision, definition: JSON.parse(row.definition_json), parentRevision: row.parent_revision, restoredFromRevision: row.restored_from_revision, digest: row.digest, createdAt: row.created_at };
|
|
3557
|
+
}
|
|
2792
3558
|
|
|
2793
3559
|
// packages/db/dist/workflow-triggers.js
|
|
2794
|
-
import { randomUUID as
|
|
3560
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
2795
3561
|
var TRIGGER_INTERVAL_MS = { hourly: 36e5, daily: 864e5, weekly: 6048e5 };
|
|
2796
3562
|
function nextWorkflowTriggerFire(startAt, cadence, now) {
|
|
2797
3563
|
const anchor = Date.parse(startAt);
|
|
@@ -2819,7 +3585,7 @@ var WorkflowTriggerStore = class {
|
|
|
2819
3585
|
}
|
|
2820
3586
|
create(input, now = Date.now()) {
|
|
2821
3587
|
const stamp = new Date(now).toISOString();
|
|
2822
|
-
const trigger = { ...input, kind: "schedule", id:
|
|
3588
|
+
const trigger = { ...input, kind: "schedule", id: randomUUID5(), version: 1, nextFireAt: nextWorkflowTriggerFire(input.startAt, input.cadence, now), createdAt: stamp, updatedAt: stamp };
|
|
2823
3589
|
this.#db.prepare("INSERT INTO workflow_triggers(id,workflow_id,enabled,next_fire_at,active_claim,trigger_json) VALUES(?,?,?,?,NULL,?)").run(trigger.id, trigger.workflowId, trigger.enabled ? 1 : 0, trigger.nextFireAt, JSON.stringify(trigger));
|
|
2824
3590
|
return trigger;
|
|
2825
3591
|
}
|
|
@@ -2845,7 +3611,7 @@ var WorkflowTriggerStore = class {
|
|
|
2845
3611
|
const current = this.get(id);
|
|
2846
3612
|
if (!current?.enabled || current.activeClaim || Date.parse(current.nextFireAt) > now)
|
|
2847
3613
|
return null;
|
|
2848
|
-
const claim =
|
|
3614
|
+
const claim = randomUUID5();
|
|
2849
3615
|
const next = { ...current, version: (current.version ?? 1) + 1, activeClaim: claim, ownerPid: process.pid, nextFireAt: nextWorkflowTriggerFire(current.startAt, current.cadence, now), lastStatus: "starting", lastExecutionId: void 0, lastError: void 0, updatedAt: new Date(now).toISOString() };
|
|
2850
3616
|
this.#db.prepare("UPDATE workflow_triggers SET active_claim=?, next_fire_at=?, trigger_json=? WHERE id=? AND enabled=1 AND active_claim IS NULL AND next_fire_at=? AND trigger_json=?").run(claim, next.nextFireAt, JSON.stringify(next), id, current.nextFireAt, JSON.stringify(current));
|
|
2851
3617
|
return this.get(id)?.activeClaim === claim ? next : null;
|
|
@@ -2910,6 +3676,14 @@ export {
|
|
|
2910
3676
|
resolveOsecRunStorage,
|
|
2911
3677
|
listOsecRunDatabasePaths,
|
|
2912
3678
|
writeOsecRunReport,
|
|
3679
|
+
LearningStoreError,
|
|
3680
|
+
learningProjectId,
|
|
3681
|
+
learningArtifactDigest,
|
|
3682
|
+
initializeLearningTables,
|
|
3683
|
+
appendLearningEvent,
|
|
3684
|
+
enqueueLearningWork,
|
|
3685
|
+
LearningStore,
|
|
3686
|
+
workflowRestorePolicy,
|
|
2913
3687
|
SecurityWorkflowStoreError,
|
|
2914
3688
|
SecurityWorkflowStore,
|
|
2915
3689
|
TRIGGER_INTERVAL_MS,
|