${escapeHtml(check.detail)}
From a0733875c4cdc252f169c11ef3cc2f5f8d5333de Mon Sep 17 00:00:00 2001 From: NuklearRabbit <145918611+NuklearRabbit@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:24:27 +0200 Subject: [PATCH] feat: deliver policy-driven Git Validator 2.0 --- SOURCE_MANIFEST.txt | 103 +++++++++++------------ main.cjs | 1 + preload.cjs | 4 + reports/architecture-audit.json | 108 +++++++++++++----------- reports/architecture-audit.md | 4 +- src/main/config-store.cjs | 39 ++++++++- src/main/git-validator-policy.cjs | 87 ++++++++++++++++++++ src/main/git-validator-service.cjs | 109 ++++++++++++++++++++++++- src/main/ipc/repository-handlers.cjs | 21 +++++ src/main/unraid-access-methods.cjs | 2 +- src/main/unraid-inventory-methods.cjs | 2 +- src/main/unraid-preflight-methods.cjs | 10 +-- src/renderer/actions/shell.js | 59 +++++++++---- src/renderer/events.js | 2 + src/renderer/mock-deployment-bridge.js | 17 ++++ src/renderer/views.js | 8 +- tests/git-validator-policy.test.mjs | 53 ++++++++++++ tests/renderer-workflow.test.mjs | 6 +- 18 files changed, 502 insertions(+), 133 deletions(-) create mode 100644 src/main/git-validator-policy.cjs create mode 100644 tests/git-validator-policy.test.mjs diff --git a/SOURCE_MANIFEST.txt b/SOURCE_MANIFEST.txt index f722ba4..5cbdcb9 100644 --- a/SOURCE_MANIFEST.txt +++ b/SOURCE_MANIFEST.txt @@ -15,40 +15,41 @@ ca32a76e708d565c4af659f0f4d2615fc32114c3f75aec1454862a3ed1e72c41 2263 25048ed854e8ce8fece115e555c98d25507b002f8019b6ae717b54604c868c50 46223 build/icon.ico 16efd2fca83004f781eae40ae0f706a004ce0bddf338dd087b8adf7eb10c1d84 85704 build/icon.png 5f4aca19a35cbcaffa1a6993ce96b7d66052ec2b286022f2af74594e8a310568 15712 CHANGELOG.md -1101ae8a14a4f0cf0a56a3a73a60e7a231e07ea7872d936904c605abf6298a34 288319 coverage/tmp/coverage-16016-1785339516187-0.json -d655dca9239887ef791e144308830fe97ae99b3531449944254d9733b27a1cae 111306 coverage/tmp/coverage-20500-1785339516683-0.json -9f13cddf4882c263f5da10e82eda888aa80d60eba5a4880646fde183ea41451f 329357 coverage/tmp/coverage-23076-1785339516366-0.json -bd1b7d71c9b2d6a89c7b96c0da51d634d7e8f9e335822f9601998eb7d1264b66 367435 coverage/tmp/coverage-30956-1785339516246-0.json -a27032411a329ff88d2ca8b0365f1c9c04f86951763bfdbdc6b1c76dcb4dbb10 327419 coverage/tmp/coverage-33844-1785339515162-0.json -59c785d1cabcee6d1069421425732d67ede026fdf907922724b894ec60f23f7c 303089 coverage/tmp/coverage-36076-1785339516543-0.json -6ccdab75e41e28365282114305041c342bf0924b488673d4afbebe8dc0b2bf4d 426817 coverage/tmp/coverage-39452-1785339516633-0.json -7019945829625166aafaf3fac38ed2095c9b6ccef4db451b103c8e641c5b31c4 301396 coverage/tmp/coverage-39516-1785339515312-0.json -204f45a7f69e83e727b5d06713b59fe7cf76fa2b8acfb9909b8a226cfebe6919 299000 coverage/tmp/coverage-41400-1785339515472-0.json -28305a502a85cc95ad37adab0c80abe7ad9923e1fb7739c8b3c01694cfd5303a 335944 coverage/tmp/coverage-43744-1785339515697-0.json -61d18db7fece14e6ffc192cd0c93556c176276c5738438a6cbc96bac4b6ebab7 282471 coverage/tmp/coverage-44320-1785339516169-0.json -10ebff421ee2f56d9a2b3ea87ec43d397609111a1e028a810472757dc1fbc6c2 346825 coverage/tmp/coverage-44612-1785339515256-0.json -f9179452c930d41972809015ef90e2d5cdbfd9a460f50420fb2131869f510f8d 354683 coverage/tmp/coverage-50832-1785339515755-0.json -6265af5161de8807eff28cba2834f6a29c93216b4b4ac3fb0955f14e7a8d677e 89173 coverage/tmp/coverage-52036-1785339515055-0.json -bd8a4f824da29801902c1f305d3804f249efb6233d44f902c04a0c35e2d4ba76 342639 coverage/tmp/coverage-52864-1785339515537-0.json -ca7431ed712d082aca6348fdeda4c67207fdab56656e0feda5fad74e60ccf4db 336007 coverage/tmp/coverage-53260-1785339515757-0.json -f834e80b44c37263816fa5ac9f29870658690453aa826db1f8a6841e5ed554b2 335634 coverage/tmp/coverage-53596-1785339519653-0.json -b8c977c8dc0f34e968c59071c376ec1e19ddc45440e3ff96d844f48eda12be05 284302 coverage/tmp/coverage-55056-1785339515769-0.json -40cad1955f780f9fd42cdcbe61d04acba2d3f7662bb27639f16e5f0820df83b3 321055 coverage/tmp/coverage-55928-1785339516726-0.json -b9559b72bc441f91eed9e65122636e5e76c8ffcb2dbfe0501fea8860d6868b12 320075 coverage/tmp/coverage-56416-1785339517848-0.json -2e027822e3475abb5caef8d1326f76d5e2f0a0512656bfd9747135a86bf1e4f3 344498 coverage/tmp/coverage-56544-1785339516170-0.json -30eada55184a94693954401a5cb9e1f37ebaee78075ed569b7ad0762aae0d123 344612 coverage/tmp/coverage-56608-1785339518494-0.json -09ac59a2ff03d78b2b025a266af53e27b67c6f33a562afba9da62b89938c8991 318324 coverage/tmp/coverage-57192-1785339516055-0.json -1b35c0e476c27889b191455271562550e146e0b4129721e8938836e9e5e25414 344933 coverage/tmp/coverage-57204-1785339516743-0.json -9fb213f290c39ec6c4d89dab2e86dde5109551a0c0f64b6b1cc020557e4a515d 302345 coverage/tmp/coverage-57868-1785339516192-0.json -993ece7e08147c4d88bce3fe791aacb6af20c487a9ad3f59f0d8ed25a0661892 304507 coverage/tmp/coverage-58136-1785339515403-0.json -344bd1ec56b83794737ea3c0c889fe5629a0baf4993d550048deb6fe1c2f1f03 352004 coverage/tmp/coverage-58704-1785339515317-0.json -3106fd06d0269f55a759c491d8d73a844ad9b13db96a0ed839407630daad1cd4 310268 coverage/tmp/coverage-58996-1785339516528-0.json -2a1b4078c040a602b68d7c684e7b4a70b330ea424866bc043baa5f3f7eb867aa 360892 coverage/tmp/coverage-59356-1785339516204-0.json -deb5210ec65db9e7aaf85bb4b3bda1ec5c9fdc89709b048712ca4a82836f84b7 358381 coverage/tmp/coverage-59756-1785339516948-0.json -3d435d5982a18f5876080c2cf0e181beb0e3759d838c429e1d1d4e6dcbe97634 302512 coverage/tmp/coverage-59972-1785339516032-0.json -0dbfa219b3a04e5854b09c803e5092b09320b2c87ebfb9c6ed69f88c2730944c 360066 coverage/tmp/coverage-59988-1785339547987-0.json -bdce38d30ef6798ae45146d58a16f794910c9016fe3f0591aa6b65613cc600a0 345121 coverage/tmp/coverage-60804-1785339522999-0.json -de23d1f8a3a19c8297c97fc44d0cccd2dff77242d7789112c5177738c0ab40a7 296834 coverage/tmp/coverage-61136-1785339515987-0.json +04a3058e507c938447d35564e503cf89b76670d8ea45f5209db074dfa2625b91 303090 coverage/tmp/coverage-14228-1785341379444-0.json +0f86a35f2e6a48c0789263a4e36190760b0dfa45ce9a1128555b4dd77da2df5b 352006 coverage/tmp/coverage-14596-1785341377618-0.json +4ae93dacf27d8ec7e84bd00ab52398a0d99cecb25cf2244be8e6d89824c3d5db 345122 coverage/tmp/coverage-15236-1785341392510-0.json +2607418ea469b58eb6190e4cabde794c0773b7746afb0fdd2d401c99a353752a 358520 coverage/tmp/coverage-17880-1785341380606-0.json +ea98763aaacb035c1c75fca8f50784fc0cc89b2dedf2ddfa80c0eb32d633af27 342639 coverage/tmp/coverage-19480-1785341377315-0.json +fdaef396ca19f7e1f62ea7e6f2172fa615c7e7f26b157e80c896199f5ba3e77e 329358 coverage/tmp/coverage-19612-1785341379233-0.json +d2808be6bce7d7d3d4662def01522170b8cef6c802813db92ade8c27b335fbbe 346825 coverage/tmp/coverage-21808-1785341377499-0.json +7f788bab2eb9ada8e3c3f7fc1d6948f001018721eccb54e87b98a8916e30695b 299000 coverage/tmp/coverage-24368-1785341377951-0.json +d3584daefb19adb8e0e7cd3ca6ffc7f6992636a692a4a3b186da94281950aa8f 302346 coverage/tmp/coverage-25148-1785341378806-0.json +e4045202501f53544351ae8f0aba6cdcccef9d08d735f4d0c83634af5d3033b3 344499 coverage/tmp/coverage-29628-1785341379071-0.json +bf66bf3c80bfed0d145dc2a73363e47089a74858d3cece5dbef2756bfbfd623b 360893 coverage/tmp/coverage-31472-1785341379151-0.json +0d52bfc33883af8fccdc37c436edd95eb83f54c65de87fa09ad03719995574e4 335635 coverage/tmp/coverage-36800-1785341386017-0.json +bcb44d6e6369e3cb5fd5306a02097fadd44e6d85eeac5490e7660f841ba86ea3 288320 coverage/tmp/coverage-37176-1785341378929-0.json +da4b2512633624ef659682bf16af77d92799bd228e0d69a985fa23246460ce09 111308 coverage/tmp/coverage-44380-1785341379522-0.json +96a9f4b2bb3a429dbe5286c7f6c13c39b0e0de882d46724853083ea3d9ac9f6e 317635 coverage/tmp/coverage-44612-1785341382147-0.json +705a92db802fac29c0bbe5aa5727ad35350ddfd03bc98efc24d68529f5a41a99 360058 coverage/tmp/coverage-44984-1785341422918-0.json +279e70e06e6366bf15bb60f6079c38580d006832ba200ba5cebd9bac8a4e4f02 321057 coverage/tmp/coverage-48188-1785341379614-0.json +20758be193868d8f9275dd1eaf35b6bfc652f4682ebc89e4c4aa481e63dd1664 301398 coverage/tmp/coverage-49592-1785341377478-0.json +a4275d02b93b13deadcc554844b321fe186ad272e676f93ccf8f84772bc2760c 284667 coverage/tmp/coverage-49952-1785341378477-0.json +d704dd8bab1e3fcf52d4e4762e6a3e4eb9818073a2fc1d622a2afe4425ec9cc4 336008 coverage/tmp/coverage-50036-1785341378337-0.json +e4dc4ec12d779464d974f7d5c9713f63db6776596698fdc4ce03d18e773a34d9 354684 coverage/tmp/coverage-50728-1785341378056-0.json +0487ef221d132f9ada2b57cb216fb0ff167f903cea8df46c61b39b9fa52bd38a 282952 coverage/tmp/coverage-51204-1785341379590-0.json +42be90ace3af568e4eace7f1a3a5974856360707839c793b3b092ede0f6c5ad1 344613 coverage/tmp/coverage-51236-1785341383324-0.json +1b5a01cc5508a78e28b27f7a46878d54aaca027deb0e93f34f445d9dd32116f1 335945 coverage/tmp/coverage-54004-1785341378407-0.json +b46e7b4d5f95b167803a7d0481163a01026b5ac2e9c32e2ba40afa8a4791163e 428588 coverage/tmp/coverage-55224-1785341380041-0.json +b175e49795c65f6011dbf04aefd75cb7ebee5c2b248edb50e7df1780f2a599b8 367436 coverage/tmp/coverage-55472-1785341379481-0.json +2a53a441228fb74c1f7a488d49c1545e53640af5c1ca720352919c86527e5c19 344934 coverage/tmp/coverage-56192-1785341380236-0.json +dd2c111bc050afda47ed7379f88651cda579a965cfed56ddf2b3ca0e5d748ee1 304508 coverage/tmp/coverage-5636-1785341377677-0.json +8ce33572e4a3ba6e63b85155dc0ebba61b3e6fd203ef17b762735639dab72467 360058 coverage/tmp/coverage-56392-1785341444543-0.json +9efac9c2f67e00fbc2ddd22ff17886ffa212b55b76e21597df1a85417e55ef0c 89175 coverage/tmp/coverage-56440-1785341377010-0.json +4b6892c586091b5bf3eead8d8687a318ef2d82874fc0c9efaab25430ee819600 296835 coverage/tmp/coverage-58592-1785341378685-0.json +81f7395f307d0c4776331a5125653388650dea21876ace362ec92e3ce1efefda 318326 coverage/tmp/coverage-58856-1785341378523-0.json +5370cdb8a3384f62e68314bd4bf0efa78d9db57ced656b011d3b1e995369596e 327419 coverage/tmp/coverage-59512-1785341377212-0.json +7e7571c84f4bcdcdd39ba6fcaa2ac9ea1583eeff6cf59ea4c46c601add76e40a 310269 coverage/tmp/coverage-60040-1785341379655-0.json +d19fda134998419dd45ecd943d587c26cf80742b140de46449f8ec4bcf9191a9 302513 coverage/tmp/coverage-61124-1785341378630-0.json 21cb96e7afe71b1dc791c818dedd244d92f9a6ed4d9ffbb3022ccb187e1bdf0f 852 docs/ACCEPTANCE.md a17f95d96d3c9fbc69d870874e6fbb7472091adefc454b24f835db1279511d72 8296 docs/ARCHITECTURE.md 72e846f591c47a0291e7466e58e052d3d5afcf551c4e6c848632ac3c552a1244 3043 docs/CURRENT_STATE.md @@ -118,17 +119,17 @@ c230b931abf2293d2d44b7a69b94c35f1142c093cc46b88739a0de5cbd6d1896 1532 106538d4a14a5a7b13419f9520c582b19809e8fafe2cb8c7dce2bc3e600dd10a 397 examples/server/nginx-forgeflow-status.conf 2dff25fb39ce8fc7844026a50524b23f241bec5b614eb05371c7f908a080f69a 398 examples/server/status-example.json 4a561ead5ba7cdfaf4efce91842a4308c5f2a77980205879d83835efb8a579db 1067 LICENSE -910e179eb2743989725e7fe18efd291ff098b4335aae4b9eb530b579efa6fd3f 13654 main.cjs +1f0f388df4397e548887bbc7579fd3c864581b86469c01703201ece7a6cbf931 13667 main.cjs 91a984a89dd57a084b9a2331763cacdb061582fb590f13df379d92c1a77a2ee1 352 OVERLAY-INSTRUCTIONS.md f3e2a262e251eb165f342dc3c184625ec10d56e536b5a987199b9944f463eee9 181069 package-lock.json 43725a143cb927cc70bd823d789788d785d872119c008043c270bbf73948851f 4882 package.json -86876cd502f51ad65e9c9280b189a80579df99ba65c81daf338048693d6ec78f 11732 preload.cjs +69318fdf054be7aa2fe86ead9847da9da65745d8d5de548c8346f3ba0afc4892 12175 preload.cjs abe5dd6fd68f2970cd19ef134094907c67219061d8fe9a1a08324c78de4ad437 484 PUBLISH-AND-ENABLE-UPDATE.cmd f018383f755352ca448e2ebb1e19b1dba412a3eb793d61e64b02953e300754fd 10538 Publish-ForgeFlow-Release.ps1 688fff7d2c989adb97ebb7fae38962656b70304a0aa5d27433c56adf7f136de0 4196 Publish-Missing-Binary-Release.ps1 794bbe1937077788f34c64398fd73dc9a3c43d095084aa32673f3e968b115de2 9150 README.md -adbed8dcc399e1c55393391f81ca6a8789840ce17ee942980d2593813aee166d 13680 reports/architecture-audit.json -9683d4988b4fbdfeef6cbb8c50e515e34c3c09bc55fb5f5913c6bb14e592f0c2 1105 reports/architecture-audit.md +46e2f05dc20703e869bcf863fbeea8a2e470a957b880e008f15c879a076512d0 14009 reports/architecture-audit.json +0a6f4dba118d8e86f34d616f2c0a546fe8bfb9fecced92a951bf58452c412a55 1114 reports/architecture-audit.md 509c7bcff5280349bd9f45ed6151f70372bad7010a9ea582c13e2ccab91fe0cd 6272 scripts/acceptance.mjs 00d57bda5af8c8eda294b72d18b318f024a307b81b0d9205a0821f5240151e31 3814 scripts/apply-binary-update.ps1 f8359a69d20deb2dfe10042d1bec7b12a95e76e58e36bc5f265f073c3111d056 10287 scripts/apply-source-update.ps1 @@ -145,7 +146,7 @@ e8c5d00737d4c5e2c37ff683e569e8d7a0510be233db3840460c78be69f2c4bc 17163 0b9f03ba3c67ff7cdb2916a902ad8ce25e81a7c90b210e4ae52d2ad029efabf3 2353 scripts/write-release-checksums.mjs 619515f524cb89960370ffcbd3fafd3c0e178b95f69c5868b1dd44777f23ec1e 2081 setup-windows.ps1 dd613d04b366f2cd071a1685a414016a5fb008082ed1b4cb8b24b79c100f640a 2412 src/main/audit-service.cjs -565787043e9825859301c203a269ca801e0c2c417f37417f0dde17238433d712 30410 src/main/config-store.cjs +04b97b9b02ad8746d2229d40ba0585088118ecd18e1b5f3a1911a4f3b63831d6 32271 src/main/config-store.cjs 2fb04b1494b39f5d7c0720fa5fd298cd46fa85dc1b696d77657592347fcf1819 2731 src/main/configuration-backup.cjs 86e9fc2eda66b4b563f6c4bbb87d3e8514340d484fb503b73137e63b6b05c3c9 14597 src/main/deploy-key-lifecycle-service.cjs 7cbfe51973d6607203cb197652ed7f296a3f6b6b644df876957117866a47d802 2159 src/main/deployment-identity.cjs @@ -153,14 +154,15 @@ dd613d04b366f2cd071a1685a414016a5fb008082ed1b4cb8b24b79c100f640a 2412 c157640e76d558906a9aa9881eda811196623ef1c65fa3467f32f0f84b0ddd0c 15095 src/main/diagnostics-service.cjs a2ef47d5330095b92c2bd22fcc39962091881f9cb60d02e261eb1dd1bd693170 1974 src/main/external-tools-service.cjs 0b7476c2cfe1872601978c20a466c20fe58be35e81b2303e38a753fea62bbc27 32548 src/main/git-service.cjs -3ce45837099ac7bddc024974bd839575b4b765a7df9055e7d45ef889dc85bf7f 15623 src/main/git-validator-service.cjs +b23dfa041d9f4597d144601ab8569e69ba748a0d7a075c3ef01154eacc2640ae 7018 src/main/git-validator-policy.cjs +ca050e9820528b555e6a1dfd89ac8be2f9a0cec6e91254897e6e3b6e116a7eb2 26317 src/main/git-validator-service.cjs 2faaef0eeec1e473db82b94243674e3812ef5869358b0ec6abf8d645a8794623 20768 src/main/gitea-service.cjs 8c3c7b9bf0893276f5ab520efba17fc143c64377128d754411cd911180025c4a 5743 src/main/inventory-classifier.cjs dafdb09133d2b6ec2161a3f0b09354551e54fc606c8107976fca37405643be91 3404 src/main/inventory-review-service.cjs 00989577aed509a7ddfdf9f4df09a196a393a85b5ea59b21089002215e69f065 25949 src/main/ipc.cjs 0eb1cfdcd3a37a0ec9502bf753966f87230c03580335798bef9265add42ee6fa 12530 src/main/ipc/deployment-handlers.cjs dc9b5971c9fefe8c374aa31916f5513601ce86003fd48b1d0e51330a909ae3a5 3442 src/main/ipc/operations-handlers.cjs -7c6628caf16228500f15309766cdbbe54f8ea8238a32052123abdc445bb7d51f 14478 src/main/ipc/repository-handlers.cjs +2e2d0b26480b395906b7881e50b596c9a7701e6e534497d04d541bf3f3b3c057 15673 src/main/ipc/repository-handlers.cjs 62f2c80c8210e19370b8556b1f296cbae50dae6b758a39e209f8fb461691fd4c 4235 src/main/log-redaction.cjs 958595a99fb242c127f475f3d8622bdba4c07b2d658703f69fe3992227a9107e 12909 src/main/preflight-service.cjs 3096b4181566cb93a27e56e248c92105d4f4df5aee39d73c6c7d8ae8c2231bc0 1570 src/main/process-runner.cjs @@ -168,12 +170,12 @@ e89b54e7e3174b4b0a1dcd9058d8344e29431f9d16d0e6bb8d11559b691440a0 2508 17e2a53f61cd7faba461b9f332967143087eaac95b72001462292976278ca305 7782 src/main/repository-service.cjs 52b6d88ed1f5c904a13cdde92e5f96d1e2b5971ceef49862152197353cdc6490 27928 src/main/server-inventory.cjs afef3841a3948b2121f8fba809aae4ea3da71bd2fda86973ba50200a5b1f89b2 14894 src/main/ssh-service.cjs -e880894fb9626d804617e8e993b88e9cdc8b9972baad4c3751e86932da45b7b3 25506 src/main/unraid-access-methods.cjs +90504e27bfabcd2f927ba29930fad9bb65520fdf871a2a9e49cbcefcb837d84c 25492 src/main/unraid-access-methods.cjs 2ede80cd1565a7f2c282cc58d35dc0889d58d7465346bc723026b9c8be4df0ac 9501 src/main/unraid-deploy-key-host.cjs 803a079499f7b8148495209dea43b505f6eb9bb587de19e82054e47183186c6e 30461 src/main/unraid-deployment-methods.cjs 2e63fdc0be0bf4d8e5c3d7d45ff5811d786b0821a08b82f511f1301696e12c9d 17157 src/main/unraid-deployment-service.cjs -08f082a2d902ce65dcf65e20a2f90631542b515eecb31cfe09520b5b0ec32ba4 35212 src/main/unraid-inventory-methods.cjs -b385460ea1ea53bb9f10463a0b036a0128efe35ef4f8a453e76181d990ef68e9 26487 src/main/unraid-preflight-methods.cjs +de0378e1344ff102552b01018451346c30c16a76eb00e7c4349c82f7b39d52a6 35210 src/main/unraid-inventory-methods.cjs +9e682411f73450f595b5cc4dfb28939c6c58b9547d0a91e287c3efb4955e8840 26299 src/main/unraid-preflight-methods.cjs d45220176aed72d692f9ae5534f9d40bcc359a2d08e025e74a3b3b505b8b9ed4 16559 src/main/unraid-runtime-methods.cjs d4b3a07eeca687a49544a94ea574f7c6f7e0bc3aa9311b614d5244a468ca4a1b 11307 src/main/unraid-state-methods.cjs b654a9e45044ad32c61fabe4a6d897288615ec83739b53e3241ff881e32f56bd 21677 src/main/update-service.cjs @@ -183,21 +185,21 @@ d0bf607dd1de9d55f2947d0adf0997cd3ca5c269d10a5362cc1d8bc4d1a2a8ae 6706 48bed91dd2a85bb51ee7307f7acc3b79c881ce8cf63b22ba79d5d079b265eb4b 7785 src/renderer/actions/inventory.js 4227a05a20580a31127d2c929640defc3d36e8e3e89d6be830aab1940da81082 12267 src/renderer/actions/recovery.js cdfaacdcd5ae04b0e5c79fefa21f5e09d5c810bcea504c5b6e1d6b744182ff84 15567 src/renderer/actions/setup-and-settings.js -b17be89568a5602d67dc226bf8ca4464731c88664de8c72a880493c96abc1963 16825 src/renderer/actions/shell.js -d33bdc89a17fbe921dacbb035dee84c9cc10c161259a02772196d9f26a17c4e9 24089 src/renderer/app.js +5d8110918b2957889047e38eb4ab2953b2b4476394d9328bd40ae6e4246e65ef 18584 src/renderer/actions/shell.js +100077a82f14d5252753d017369ecef48ed6d00532166ae5a0b356ead549e02b 24087 src/renderer/app.js 16efd2fca83004f781eae40ae0f706a004ce0bddf338dd087b8adf7eb10c1d84 85704 src/renderer/assets/itworx-mark.png 813b8cdeecac43794166f3db9d3c5d2c441e0292f9ab7bd465ba136d6201e95d 82476 src/renderer/assets/itworx-wordmark-dark.png 094c1b71cc2482a9db250ac175f45f3de68f53277dfbde371a03e61923d00988 75240 src/renderer/assets/itworx-wordmark-light.png 813b8cdeecac43794166f3db9d3c5d2c441e0292f9ab7bd465ba136d6201e95d 82476 src/renderer/assets/itworx-wordmark.png 5d6c87d51e55c9e976cf83ce242be0c28cf831d9c52082f02e745193500e6348 47992 src/renderer/dialogs.js -7abdd25671edcc63b134a13f013890c67a5988d2d12c0e969506d75abe83ec3c 5630 src/renderer/events.js +977af9585074f0a404830c5a93de6939b4897d2eb98ba8ae75c3cf0023dea673 5782 src/renderer/events.js a84da5aecbb16ce7983dba1f6d6aab1bf47b2e9a87c2933fa1afb8123f7ef7d6 1497 src/renderer/index.html 06180d9656dd254edfb6949c397f8e313954fc560ddcb22b3a35fce3c3e35655 21350 src/renderer/mock-bridge.js -6da0d3e9fcbe6040cf30bdefb566dd8a7a26df2352233124abd8a011034f477e 26667 src/renderer/mock-deployment-bridge.js +19603be9f471ba695c66f17fc027ab427b68ce146af80d52e3c94624a5ba1c35 27712 src/renderer/mock-deployment-bridge.js 26065ffa2359cd27b9c9b5b9fb67bcad83ba0f118960c7c024e7e9392dbb16a3 20032 src/renderer/mock-repository-bridge.js 94fa265c2fe9ca8d644f0ce9b620b6f85d9b25dca5802c4e9195b66dcbe80120 6522 src/renderer/operations.js 45692591428575b518678a6b548c25d3de95f568541e5648b0f06f42c48b5bbf 77872 src/renderer/styles.css -314d0db4d5ae2042ab76516daf452bc7c49c3e667dfc06c731dd3f47b153e12a 93872 src/renderer/views.js +1703e64533b7e2717b27c5776296c7dd76331e6f97e8005aea9fd688f1aee3ae 94834 src/renderer/views.js 0a1e9d9d6cd4d190eb7f85dbc6668d80600b1cf2749cc0c2c51cc428f506f20d 1121 src/shared/clone-target.cjs 5d425d5c2f939d0f6beebee7ebb0c77146cb7e318535ba7286ec7081a4dc2269 2497 src/shared/deployment-policy.cjs 029e600229714d033c28e2dcb77817aa8269847001782ae0012960e83ffd183f 3057 src/shared/git-status.cjs @@ -220,6 +222,7 @@ fae3634bae871abade4d487b94b4741b50e787804dbd6135249f634fdd83c6d0 3800 dd121d96ca265a027cd415a52064500a4541b2f8a662f4f4b25f2f996d52b5da 762 tests/external-tools.test.mjs e7aebcc0d484a6a59d463d5cb26c11b3ad56e28f6535e7c38a0fe166a41565ea 13690 tests/git-integration.test.mjs 5ea94c6b241a02060d531fad94e449eecd3772eed2137581d4e2babfb09e56db 1239 tests/git-status.test.mjs +c00bbd8eae5cef7856c8283d6b40dedb81083bf57ad762e89ab79e0f312da351 3271 tests/git-validator-policy.test.mjs 73d00729696e5067ba33dd6d43b018d89ce7fdd561a60ab66648d3283fb54d21 5370 tests/git-validator.test.mjs 681ab7bcd02c4dd98d1d8d2092a3521c489d941131e7ffe5903971b940046474 2403 tests/git-workflows.test.mjs 771eeb4fa5443d581991cedb4107d7c414ce1b7d2e14bac7cf719ec8ba131647 8969 tests/gitea-actions.test.mjs @@ -228,7 +231,7 @@ e7aebcc0d484a6a59d463d5cb26c11b3ad56e28f6535e7c38a0fe166a41565ea 13690 caf98cbd9de9b119dae610ee53fa333a7a11214f34762247452fbb85e8bbf725 2392 tests/log-redaction.test.mjs 96432a97d313f331694900bf0a2c21e38c20eac96d59147977aeed9055a9e3ad 2287 tests/partial-staging.test.mjs c0f8f5a3784835f19d9ff1015185ccb385840b6fa1c9ec19f233393a7d952b65 3718 tests/preflight.test.mjs -676a5a791e94dcda11ee1f1826d5c4de2f94389072b4f53a5228a57cb0c5d9e0 9493 tests/renderer-workflow.test.mjs +629ba26395c0b49cc5fdee6b0646d75369eb6338e1cc7b59509938f97eea08ec 9601 tests/renderer-workflow.test.mjs 2b4956fa4df4624a04117737e57ba74020564330ff71303b5746d8ccc881e880 854 tests/repository-matching.test.mjs f679072548554a64974f0452337ce5e7b0c567343c287223770cc0974b905348 1068 tests/repository-monitor.test.mjs 75b5b83836c75675bb9a48fe4363fcb8a24fc425e6af6f822d7955c6f3c79eac 2265 tests/repository-service.test.mjs diff --git a/main.cjs b/main.cjs index e76d425..bb5f94d 100644 --- a/main.cjs +++ b/main.cjs @@ -300,6 +300,7 @@ app git, gitea, diagnostics, + store, }); repositoryMonitor = new RepositoryMonitor({ store, diff --git a/preload.cjs b/preload.cjs index 848e9ff..1839444 100644 --- a/preload.cjs +++ b/preload.cjs @@ -142,6 +142,10 @@ contextBridge.exposeInMainWorld( troubleshooterRepair: (issue) => invoke('troubleshooter:repair', { issue }), troubleshooterAutoRepair: (issues) => invoke('troubleshooter:auto-repair', { issues }), gitValidatorScan: (fullName) => invoke('git-validator:scan', { fullName }), + gitValidatorSetPolicy: (fullName, policy) => invoke('git-validator:set-policy', { fullName, policy }), + gitValidatorSuppress: (fullName, suppression) => invoke('git-validator:suppress', { fullName, suppression }), + gitValidatorPreviewRepair: (fullName, check) => invoke('git-validator:preview-repair', { fullName, check }), + gitValidatorExport: (fullName, format = 'json') => invoke('git-validator:export', { fullName, format }), gitValidatorRepair: (fullName, check) => invoke('git-validator:repair', { fullName, check }), diagnosticsStatus: () => invoke('diagnostics:status'), clearDiagnostics: () => invoke('diagnostics:clear'), diff --git a/reports/architecture-audit.json b/reports/architecture-audit.json index b2945c4..8c61d00 100644 --- a/reports/architecture-audit.json +++ b/reports/architecture-audit.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-07-29T16:10:50.148Z", + "generatedAt": "2026-07-29T16:20:01.552Z", "thresholds": { "preferredMaximumLines": 750, "justificationRequiredLines": 1000 @@ -7,6 +7,21 @@ "over750": [], "over1000": [], "cyclomaticHotspots": [ + { + "file": "src/renderer/actions/shell.js", + "lines": 506, + "branches": 98, + "functions": 84, + "ipcHandlers": 0, + "responsibilities": [ + "inventory", + "deployment", + "git", + "renderer", + "updates" + ], + "hotspotScore": 128 + }, { "file": "src/main/server-inventory.cjs", "lines": 578, @@ -22,20 +37,6 @@ ], "hotspotScore": 119 }, - { - "file": "src/renderer/actions/shell.js", - "lines": 481, - "branches": 89, - "functions": 81, - "ipcHandlers": 0, - "responsibilities": [ - "inventory", - "deployment", - "git", - "renderer" - ], - "hotspotScore": 109 - }, { "file": "src/main/git-service.cjs", "lines": 632, @@ -49,6 +50,21 @@ } ], "mixedResponsibilityModules": [ + { + "file": "src/renderer/actions/shell.js", + "lines": 506, + "branches": 98, + "functions": 84, + "ipcHandlers": 0, + "responsibilities": [ + "inventory", + "deployment", + "git", + "renderer", + "updates" + ], + "hotspotScore": 128 + }, { "file": "src/main/server-inventory.cjs", "lines": 578, @@ -66,7 +82,7 @@ }, { "file": "src/renderer/app.js", - "lines": 669, + "lines": 667, "branches": 74, "functions": 110, "ipcHandlers": 0, @@ -80,20 +96,6 @@ ], "hotspotScore": 114 }, - { - "file": "src/renderer/actions/shell.js", - "lines": 481, - "branches": 89, - "functions": 81, - "ipcHandlers": 0, - "responsibilities": [ - "inventory", - "deployment", - "git", - "renderer" - ], - "hotspotScore": 109 - }, { "file": "src/main/ipc.cjs", "lines": 749, @@ -128,8 +130,8 @@ }, { "file": "src/renderer/views.js", - "lines": 695, - "branches": 52, + "lines": 693, + "branches": 53, "functions": 138, "ipcHandlers": 0, "responsibilities": [ @@ -140,7 +142,7 @@ "security", "updates" ], - "hotspotScore": 92 + "hotspotScore": 93 }, { "file": "src/renderer/actions/setup-and-settings.js", @@ -203,7 +205,7 @@ }, { "file": "main.cjs", - "lines": 442, + "lines": 443, "branches": 33, "functions": 53, "ipcHandlers": 0, @@ -236,9 +238,9 @@ }, { "file": "src/main/config-store.cjs", - "lines": 615, + "lines": 652, "branches": 47, - "functions": 82, + "functions": 86, "ipcHandlers": 0, "responsibilities": [ "inventory", @@ -341,9 +343,9 @@ }, { "file": "src/renderer/events.js", - "lines": 170, - "branches": 30, - "functions": 25, + "lines": 172, + "branches": 31, + "functions": 26, "ipcHandlers": 0, "responsibilities": [ "inventory", @@ -352,6 +354,20 @@ "renderer", "security" ], + "hotspotScore": 61 + }, + { + "file": "src/main/git-validator-service.cjs", + "lines": 588, + "branches": 40, + "functions": 74, + "ipcHandlers": 0, + "responsibilities": [ + "deployment", + "git", + "security", + "updates" + ], "hotspotScore": 60 }, { @@ -429,9 +445,9 @@ }, { "file": "preload.cjs", - "lines": 158, + "lines": 162, "branches": 2, - "functions": 119, + "functions": 123, "ipcHandlers": 0, "responsibilities": [ "inventory", @@ -446,9 +462,9 @@ }, { "file": "src/renderer/mock-deployment-bridge.js", - "lines": 661, + "lines": 678, "branches": 11, - "functions": 83, + "functions": 87, "ipcHandlers": 0, "responsibilities": [ "inventory", @@ -655,10 +671,10 @@ }, { "file": "src/main/ipc/repository-handlers.cjs", - "lines": 390, + "lines": 411, "branches": 16, - "functions": 75, - "ipcHandlers": 47, + "functions": 79, + "ipcHandlers": 51, "responsibilities": [ "git", "ipc" diff --git a/reports/architecture-audit.md b/reports/architecture-audit.md index d39937e..58db484 100644 --- a/reports/architecture-audit.md +++ b/reports/architecture-audit.md @@ -1,6 +1,6 @@ # ForgeFlow architecture audit -Generated 2026-07-29T16:10:50.148Z. Complexity is a deterministic decision-point count used for hotspot ranking, not a claim of exact McCabe complexity. +Generated 2026-07-29T16:20:01.552Z. Complexity is a deterministic decision-point count used for hotspot ranking, not a claim of exact McCabe complexity. ## Files above 750 lines @@ -18,8 +18,8 @@ No findings. | File | Lines | Decisions | Functions | IPC handlers | Responsibilities | |---|---:|---:|---:|---:|---| +| `src/renderer/actions/shell.js` | 506 | 98 | 84 | 0 | inventory, deployment, git, renderer, updates | | `src/main/server-inventory.cjs` | 578 | 89 | 104 | 0 | inventory, deployment, git, security, updates | -| `src/renderer/actions/shell.js` | 481 | 89 | 81 | 0 | inventory, deployment, git, renderer | | `src/main/git-service.cjs` | 632 | 98 | 109 | 0 | git | ## Interpretation diff --git a/src/main/config-store.cjs b/src/main/config-store.cjs index d255ce5..432735a 100644 --- a/src/main/config-store.cjs +++ b/src/main/config-store.cjs @@ -7,7 +7,7 @@ const { safeStorage } = require('electron'); const { assertHttpUrl, assertWorkflowFileName, assertBranchName, assertEnvironmentName, assertCloneRemote, assertRepositoryRelativePath, assertRepositoryRelativePaths } = require('../shared/validation.cjs'); const DEFAULT_CONFIG = { - schemaVersion: 12, + schemaVersion: 13, setupComplete: false, appearance: 'dark', gitea: { baseUrl: '', user: null, encryptedToken: null }, @@ -16,6 +16,7 @@ const DEFAULT_CONFIG = { deploymentProfiles: {}, deploymentStates: {}, inventoryReviewDecisions: {}, + gitValidator: { policies: {}, suppressions: {}, trends: {} }, favorites: [], updates: { owner: 'Jens', @@ -67,6 +68,11 @@ class ConfigStore { workspaceRoots: uniqueStrings(source.workspaceRoots), repositoryMappings: source.repositoryMappings && typeof source.repositoryMappings === 'object' ? source.repositoryMappings : {}, inventoryReviewDecisions: source.inventoryReviewDecisions && typeof source.inventoryReviewDecisions === 'object' ? structuredClone(source.inventoryReviewDecisions) : {}, + gitValidator: { + policies: source.gitValidator?.policies && typeof source.gitValidator.policies === 'object' ? structuredClone(source.gitValidator.policies) : {}, + suppressions: source.gitValidator?.suppressions && typeof source.gitValidator.suppressions === 'object' ? structuredClone(source.gitValidator.suppressions) : {}, + trends: source.gitValidator?.trends && typeof source.gitValidator.trends === 'object' ? structuredClone(source.gitValidator.trends) : {} + }, deploymentProfiles: source.deploymentProfiles && typeof source.deploymentProfiles === 'object' ? Object.fromEntries(Object.entries(source.deploymentProfiles).map(([key, profiles]) => [key, (Array.isArray(profiles) ? profiles : []).map((profile) => { if (!profile || typeof profile !== 'object' || profile.provider !== 'ssh-unraid') return profile; @@ -144,6 +150,36 @@ class ConfigStore { return this.saveQueue; } + getGitValidatorState(fullName) { + const key = String(fullName || '').toLowerCase(); + return { + policy: structuredClone(this.data.gitValidator.policies[key] || { id: 'standard' }), + suppressions: structuredClone(this.data.gitValidator.suppressions[key] || []), + trends: structuredClone(this.data.gitValidator.trends[key] || []) + }; + } + + async setGitValidatorPolicy(fullName, policy) { + const key = String(fullName || '').toLowerCase(); + this.data.gitValidator.policies[key] = structuredClone(policy); + await this.save(); + return this.getGitValidatorState(key); + } + + async addGitValidatorSuppression(fullName, suppression) { + const key = String(fullName || '').toLowerCase(); + this.data.gitValidator.suppressions[key] = [...(this.data.gitValidator.suppressions[key] || []), structuredClone(suppression)].slice(-250); + await this.save(); + return this.getGitValidatorState(key); + } + + async appendGitValidatorTrend(fullName, trend) { + const key = String(fullName || '').toLowerCase(); + this.data.gitValidator.trends[key] = [...(this.data.gitValidator.trends[key] || []), structuredClone(trend)].slice(-100); + await this.save(); + return this.getGitValidatorState(key); + } + async createRecoverySnapshot(reason = 'configuration-change') { await this.saveQueue.catch(() => {}); const safeReason = String(reason || 'configuration-change').toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 80) || 'configuration-change'; @@ -602,6 +638,7 @@ class ConfigStore { repositoryMappings: { ...this.data.repositoryMappings }, deploymentProfiles: structuredClone(this.data.deploymentProfiles), deploymentStates: structuredClone(this.data.deploymentStates), + gitValidator: structuredClone(this.data.gitValidator), favorites: [...this.data.favorites], updates: { ...this.data.updates }, servers: this.data.servers.map((server) => this.getPublicServer(server)), diff --git a/src/main/git-validator-policy.cjs b/src/main/git-validator-policy.cjs new file mode 100644 index 0000000..80cfb91 --- /dev/null +++ b/src/main/git-validator-policy.cjs @@ -0,0 +1,87 @@ +"use strict"; + +const crypto = require("node:crypto"); + +const PROFILE_DEFINITIONS = Object.freeze({ + minimal: { label: "Minimal", requiredScore: 55, severities: ["error"], allowSuppressions: true, maxSuppressionDays: 180 }, + standard: { label: "Standard", requiredScore: 70, severities: ["error", "warning"], allowSuppressions: true, maxSuppressionDays: 90 }, + strict: { label: "Strict", requiredScore: 82, severities: ["error", "warning"], allowSuppressions: true, maxSuppressionDays: 30 }, + production: { label: "Production", requiredScore: 90, severities: ["error", "warning"], allowSuppressions: true, maxSuppressionDays: 14 }, +}); + +function normalizePolicy(policy = {}) { + const id = String(policy.id || policy.profile || "standard").toLowerCase(); + const base = PROFILE_DEFINITIONS[id] || PROFILE_DEFINITIONS.standard; + const custom = id === "organization" ? policy : {}; + return { + id, + label: custom.label || base.label || "Organization custom", + requiredScore: Math.min(100, Math.max(0, Number(custom.requiredScore ?? base.requiredScore ?? 80))), + enabledChecks: Array.isArray(custom.enabledChecks) ? [...new Set(custom.enabledChecks.map(String))] : null, + severityOverrides: custom.severityOverrides && typeof custom.severityOverrides === "object" ? { ...custom.severityOverrides } : {}, + blockingChecks: [...new Set((custom.blockingChecks || policy.blockingChecks || []).map(String))], + allowSuppressions: custom.allowSuppressions ?? base.allowSuppressions ?? true, + maxSuppressionDays: Math.max(1, Number(custom.maxSuppressionDays ?? base.maxSuppressionDays ?? 30)), + }; +} + +function validateSuppression(input, policy, now = new Date()) { + if (!policy.allowSuppressions) throw new Error("The selected policy does not allow suppressions."); + const checkId = String(input?.checkId || "").trim(); + const reason = String(input?.reason || "").trim(); + const author = String(input?.author || "").trim(); + const scope = String(input?.scope || "repository").trim(); + const evidence = String(input?.evidence || "").trim(); + const expiresAt = new Date(input?.expiresAt || ""); + if (!checkId || reason.length < 10 || !author || !evidence) throw new Error("A suppression requires a check ID, author, evidence and a reason of at least 10 characters."); + if (!Number.isFinite(expiresAt.getTime()) || expiresAt <= now) throw new Error("A suppression expiry must be in the future."); + const maximum = new Date(now.getTime() + policy.maxSuppressionDays * 86_400_000); + if (expiresAt > maximum) throw new Error(`This policy permits suppressions for at most ${policy.maxSuppressionDays} days.`); + return { + id: crypto.randomUUID(), checkId, reason, author, + createdAt: now.toISOString(), ticket: String(input.ticket || "").trim() || null, + expiresAt: expiresAt.toISOString(), scope, evidence, + }; +} + +function applyPolicy(checks, policyInput, suppressions = [], now = new Date()) { + const policy = normalizePolicy(policyInput); + const enabled = policy.enabledChecks ? new Set(policy.enabledChecks) : null; + const relevant = checks.filter((check) => !enabled || enabled.has(check.id)).map((check) => { + const status = policy.severityOverrides[check.id] || check.status; + const suppression = suppressions.find((item) => item.checkId === check.id && new Date(item.expiresAt) > now); + const expiredSuppression = suppressions.find((item) => item.checkId === check.id && new Date(item.expiresAt) <= now); + return { + ...check, + status, + suppressed: Boolean(suppression), + suppression: suppression || null, + expiredSuppression: expiredSuppression || null, + blocking: !suppression && status !== "pass" && (status === "error" || policy.blockingChecks.includes(check.id)), + }; + }); + return { policy, checks: relevant }; +} + +function buildTrend(previous, report) { + const prior = new Map((previous?.checks || []).map((check) => [check.id, check])); + const current = new Map(report.checks.map((check) => [check.id, check])); + const active = (check) => check && check.status !== "pass" && !check.suppressed; + const newlyFound = [...current.values()].filter((check) => active(check) && !active(prior.get(check.id))).map((check) => check.id); + const resolved = [...prior.values()].filter((check) => active(check) && !active(current.get(check.id))).map((check) => check.id); + const regressions = [...current.values()].filter((check) => active(check) && prior.get(check.id)?.status === "warning" && check.status === "error").map((check) => check.id); + return { score: report.score, categories: report.categories, newlyFound, resolved, regressions, suppressions: report.checks.filter((check) => check.suppressed).map((check) => check.id), checkedAt: report.checkedAt, commitSha: report.commitSha || null, checks: report.checks.map(({ id, status, suppressed }) => ({ id, status, suppressed })) }; +} + +function exportReport(report, format = "json") { + if (format === "json") return { extension: "json", mimeType: "application/json", content: `${JSON.stringify(report, null, 2)}\n` }; + const rows = report.checks.map((check) => `| ${check.id} | ${check.category} | ${check.status}${check.suppressed ? " (suppressed)" : ""} | ${String(check.detail).replace(/\|/g, "\\|")} |`).join("\n"); + const markdown = `# Git assurance report — ${report.repository}\n\nPolicy: **${report.policy.label}** · Score: **${report.score}/100** · Commit: \`${report.commitSha || "unknown"}\`\n\n| Check | Category | Status | Evidence |\n|---|---|---|---|\n${rows}\n`; + if (format === "markdown") return { extension: "md", mimeType: "text/markdown", content: markdown }; + if (format !== "html") throw new Error("Unsupported Git Validator export format."); + const escape = (value) => String(value).replace(/[&<>"']/g, (character) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[character]); + const htmlRows = report.checks.map((check) => `
Policy: ${escape(report.policy.label)} · Score: ${report.score}/100 · Commit: ${escape(report.commitSha || "unknown")}
| Check | Category | Status | Evidence |
|---|
Inspect repository identity, branch governance, tracked secrets, file hygiene and safe local synchronization settings.
${report.summary.passed} passed · ${report.summary.warnings} recommendations · ${report.summary.errors} critical
${report.summary.passed} passed · ${report.summary.warnings} recommendations · ${report.summary.errors} critical
${escapeHtml(check.detail)}
${escapeHtml(check.detail)}
${check.suppressed ? `Suppressed until ${formatDate(check.suppression.expiresAt)} · ${escapeHtml(check.suppression.reason)}` : check.expiredSuppression ? `Exception expired; finding is active again.` : ""}