test: add isolated production acceptance harness

This commit is contained in:
NuklearRabbit
2026-07-29 18:55:44 +02:00
parent 1e86afb7d9
commit dcbd461a92
7 changed files with 360 additions and 46 deletions
+41 -39
View File
@@ -17,42 +17,42 @@ ca32a76e708d565c4af659f0f4d2615fc32114c3f75aec1454862a3ed1e72c41 2263
25048ed854e8ce8fece115e555c98d25507b002f8019b6ae717b54604c868c50 46223 build/icon.ico 25048ed854e8ce8fece115e555c98d25507b002f8019b6ae717b54604c868c50 46223 build/icon.ico
16efd2fca83004f781eae40ae0f706a004ce0bddf338dd087b8adf7eb10c1d84 85704 build/icon.png 16efd2fca83004f781eae40ae0f706a004ce0bddf338dd087b8adf7eb10c1d84 85704 build/icon.png
5f4aca19a35cbcaffa1a6993ce96b7d66052ec2b286022f2af74594e8a310568 15712 CHANGELOG.md 5f4aca19a35cbcaffa1a6993ce96b7d66052ec2b286022f2af74594e8a310568 15712 CHANGELOG.md
56691e95912beab52b9e072ad0735a706934def093d91a83a235119e03f7ef21 111308 coverage/tmp/coverage-14112-1785342182302-0.json 7566acd80c7e2669a742aa8e6fc51cff37e20fc9f639e05a45849652a3ec5e72 111308 coverage/tmp/coverage-13332-1785343677212-0.json
35e206d8e448f56715fdeaafc82a00680df74edce1bd1d74d2c626c0ebdc8dd4 344934 coverage/tmp/coverage-18504-1785342182921-0.json 710f8c8890ba5ba34d3019cba54c9a1e92d70b40ae922819c266a05e33864d14 346825 coverage/tmp/coverage-24720-1785343674910-0.json
cd5a1817ee7f405f72ede736ec2ccc5831a14c4ab99c3ae9e1af11c93c7f4c13 354684 coverage/tmp/coverage-23740-1785342180573-0.json 7243038d2d2a14694a19322e3f45463eb7ec60e73fe6d5f8c8d32940016aa3ab 344613 coverage/tmp/coverage-24932-1785343683161-0.json
f2dbe68eb60983b2038bfda8448e148e0d762794651c8f0ef0a5985549f35d11 301398 coverage/tmp/coverage-30356-1785342180023-0.json 93180bb5e2d2242067d9d8552ebeb58b2c3d4bee446a336298a2ffbd70415066 375705 coverage/tmp/coverage-25852-1785343688031-0.json
04e0ac6a30fdcbbc9f9856697b228626e0355b7c98c6045cf0db01de0c93fc68 344613 coverage/tmp/coverage-35156-1785342187759-0.json 703a2c535f43ec8ed5275511d58f2b70a233d1d154197f582a6acb38f84f0fb4 329358 coverage/tmp/coverage-27392-1785343676373-0.json
0d4e6a6dcc0ce9f8d2d9e304dbbeccc2b551fbe79637fdd16b7e860a58685d68 302513 coverage/tmp/coverage-35780-1785342181205-0.json ea173be56e3c7309ccfbd411387aa13e171583f088c2b98d7ceedc41174feeaf 344499 coverage/tmp/coverage-30564-1785343676182-0.json
3c5a7491afc0247568bce0824331c16e2e9af0693b216e01fe73bb41ad4fe91f 327420 coverage/tmp/coverage-38404-1785342179951-0.json 2af0d14483ea6a68c1da5f8dbd3c18b1ae300250c4e5d577da8757fd041e0beb 89175 coverage/tmp/coverage-33800-1785343673834-0.json
24ca89f314bacd35321b8772706e5c4658602abf90e701561d23f67a1d4dd10e 288320 coverage/tmp/coverage-38780-1785342181696-0.json 63ed613aa107c5cb7f94612bc03034cfd09e09ee7843df839c36f7c63d6af614 335945 coverage/tmp/coverage-39536-1785343675374-0.json
8711b58d90963d5c5239011527a8d504fde3bed93671e09b8e6f8e865837d928 358165 coverage/tmp/coverage-39472-1785342183215-0.json c20ef6f15b2258814a1edc635a73eae6a8a5ce8f797299b4862090d74cd2a4ca 358048 coverage/tmp/coverage-41000-1785343679480-0.json
5e8d4cd9c0b348618088733e133986c8bff8d822b7a0f0fb6c04a777f025e249 303090 coverage/tmp/coverage-39536-1785342182261-0.json 3d91c5a2a5c337ee043fd69057dc272d13f2fc6b4449f602e6258a7058a6a737 282952 coverage/tmp/coverage-42856-1785343676163-0.json
9aa7a9e3b5daa4a3f2139df2a8b8857d851ef2ce8e3c7679db787f6e72f5df38 302346 coverage/tmp/coverage-41596-1785342181753-0.json 9c4e63126e21fc5ad77aacee02f4f0ee11ed4f1e082c42058e87c994aaef042e 345345 coverage/tmp/coverage-44284-1785343695928-0.json
cd9419f7829a06e3932ca774fb06c51b3637639e8f750fe0bbce400921c20866 321057 coverage/tmp/coverage-42088-1785342182383-0.json f3fce97506f8d0160895eb4c3e02294d7c75e0a9dce87e3dc947144642c94974 303090 coverage/tmp/coverage-44348-1785343676823-0.json
5b75886d617be03df5d1271223cbd8cc660d6603368349d7f7740e7b5005d3d2 304508 coverage/tmp/coverage-42724-1785342180308-0.json d1228bdd74c66ed3209840f0b709e1fb03f7644a6ff4f4d519d1861753be6da0 327420 coverage/tmp/coverage-44412-1785343674123-0.json
e567e65494a3fadf0effdd7058761c890ff35411532673fb6d0feb0f7d0d681f 346823 coverage/tmp/coverage-44760-1785342180156-0.json a664eb5689351c9ed939c9c4be41b837cb836af5c05833cdcd7d175661622728 321055 coverage/tmp/coverage-45232-1785343677332-0.json
4f2a6563b9710b23c918d10f1a2d713975f0f5d2feedb5731e87a441bb9eabae 352006 coverage/tmp/coverage-44924-1785342180167-0.json d111623040499c4f544ce4b51aa8f908a8cbeb0e58ea471cbf71bfa90d6ba0a3 302513 coverage/tmp/coverage-46296-1785343675742-0.json
5e355c76dab29a1ecebe30d390970397ae6ccd71798b111fa67e395a1310ad5e 361403 coverage/tmp/coverage-46136-1785342180786-0.json 207bf630211c89083cb7746522b62010bccdd8cec1826d83b064627b2d35c496 354684 coverage/tmp/coverage-48416-1785343675186-0.json
1623101020fed0786a83d5244a03df9bf2124a9a01961a7739ce63ba7c941ca4 336008 coverage/tmp/coverage-47576-1785342181138-0.json 23cd47bb0313aa849ff329f2d15208a50bc36b5de74d602d37aed34ce213fbae 428588 coverage/tmp/coverage-54644-1785343677776-0.json
0307f0113c6b76b5044cae887f76f3c01cfd7133fd4a00a8591a979eff3ab06e 317635 coverage/tmp/coverage-48580-1785342185938-0.json d68bb63951f5cc6a1bddc60abdf45a47d37198895a4d6e7bcd4f1b091a34c941 361403 coverage/tmp/coverage-54916-1785343676205-0.json
4588ba2d6ef043d6552135f874f2415865f2401a53c46c6e29229b6925deceb0 335945 coverage/tmp/coverage-48608-1785342180830-0.json 9aea05800551c378d49a72ef3dfc0bed41758596f564f8f7f5770241529f9e50 301398 coverage/tmp/coverage-55996-1785343674664-0.json
9335bccbbea8122e6bcd7190e5266e4a0fbd1e3631d882004e2d43cec41de95d 318326 coverage/tmp/coverage-49480-1785342181092-0.json e60da912d861a7e03cbfa62075767a40299ec7c9bc2e48953813abd809ffb167 299001 coverage/tmp/coverage-56060-1785343674805-0.json
e82fa624b1ef3e7ea427b58aac1c3135d0ac1af62b5c1768a7c2bd59791765c7 367436 coverage/tmp/coverage-50664-1785342182886-0.json 2d232045fb33bb89d6a4231f685a4adf97db35204d0ac32ee4c25fc082c4f662 302346 coverage/tmp/coverage-56488-1785343676124-0.json
0884915ee6ccec377161cb960b768bb44da6f8622ab3d9542a0c51a9fd0106b9 375705 coverage/tmp/coverage-54052-1785342191177-0.json 5fc972ba40cfe569a4bbb03a43b9f56099f5264b50ae1dcdd10054afb368426b 288320 coverage/tmp/coverage-59812-1785343676345-0.json
a0150efac6b758945f87373f8329f05d6be53d11a3201a88a1124372e8ee7d69 282952 coverage/tmp/coverage-54676-1785342182110-0.json ebb618c5693423e51ca20a70eda2b0da8521ea69dd14fcdd99fcf519701eaca9 367436 coverage/tmp/coverage-59972-1785343676795-0.json
885fb349d550fa169fb0a0fe97c96c9f0353e62a321a2022da2de0f1c1958811 345124 coverage/tmp/coverage-56728-1785342195847-0.json 181336e5fe71a06897acf7ba8068dd216641e38ad8c245d0ce717197e71432a5 317635 coverage/tmp/coverage-60580-1785343681448-0.json
e0dbe28900b09fc36b22fbb05bc844f57ff3094b9a1f84da97f793311c37b3f9 344499 coverage/tmp/coverage-57256-1785342181830-0.json 729e4f8079ece6dfea24a86fbc80bc7a8f93fe5cf7ac8723b761f058ee7a888e 296835 coverage/tmp/coverage-61120-1785343676088-0.json
615cc6516905c8e53e4b888bc81589ce8542e34634e5a391ba9204843f57221b 428589 coverage/tmp/coverage-58964-1785342182533-0.json 1d241ced91a381b3fbef67809960b7056cde581754213158d7ca7b19f20f4ca2 304508 coverage/tmp/coverage-62032-1785343674755-0.json
5d845ca1a0ff28f1e570dcb8a26502e902589eafc2727171f1aad0aa7a2ce52e 296835 coverage/tmp/coverage-59284-1785342181255-0.json 576d439708904311a09bd5b1aaa45f5a8bb15c79c22f8878e60b3104c24d5342 330765 coverage/tmp/coverage-62256-1785343674960-0.json
3afca2678ab83c1ecaba8c1abf70b89b3763016acb52df4027d61a590dcf5c19 284668 coverage/tmp/coverage-59848-1785342181206-0.json 6bca1a6ee829b5f9b463249b74f71525bd0f2ddd7e0ae547fc5f400e07cc76a3 336008 coverage/tmp/coverage-62952-1785343675508-0.json
5a39c973f886c05b9c34ca089f9d410ab5831036e83d7d5dbf525c2feb0e3670 342640 coverage/tmp/coverage-60948-1785342181050-0.json cc27b4cbdb3861260fefcaead1889aaddde36edc6c5dc6829ed22e6dfb8dd8ff 342640 coverage/tmp/coverage-63104-1785343675273-0.json
79d1a3635a8c972ac6ab0aa75149b2a5f01b84ae2a0ae68e736490160b22bc27 360058 coverage/tmp/coverage-61560-1785342250533-0.json 53851db4e6557d92a689c11ef1a3043db4d6071a4a73622a93f8229bb113c0e7 360058 coverage/tmp/coverage-64188-1785343739320-0.json
03adfd7ea1161c638fe6f6a75a0ab0d860f89412ea047209ea1b15f879de9aad 299000 coverage/tmp/coverage-62420-1785342180409-0.json 7aaf7345348821f50b7be27e118b3e6a387bf999d5d450f4718cd29ba7553b2f 284668 coverage/tmp/coverage-65604-1785343675475-0.json
8387e10a857a2195458bf576c4129b2cd516a50ac5408a605eff038376bba64b 310269 coverage/tmp/coverage-62428-1785342182350-0.json bb8f50356c195d0dd8684a0f84f4e8d2b03f5ebbc3d84cafcb5f18cd3fe04e45 352006 coverage/tmp/coverage-65608-1785343674667-0.json
a02d7c51b209b5967e63cec657caeb8e671b5c87d34dbe2cfbec2be891f6266e 329358 coverage/tmp/coverage-62604-1785342181856-0.json 4fc5bee2e42ba4d4af73e86930047cf66b712389698720a9ee72716c6869cba6 318326 coverage/tmp/coverage-65784-1785343677118-0.json
3488e63749aed14a820a72d8fad385175f2111648bdf2c10a48c1148c419836f 330765 coverage/tmp/coverage-62652-1785342180356-0.json bb0881ad48a080a2ad79126316cb040b1285b097987bbf71526fe42f3515fb94 344934 coverage/tmp/coverage-8144-1785343677466-0.json
3da8a2ab49221e4c837c413af878b951f1011be09824ee7dcb49485fb40550bd 89175 coverage/tmp/coverage-63428-1785342179556-0.json a1ee0025dfff97ef9eeb1e418317ecc7fbbc65afc6312b03bded8780cdb3db24 310269 coverage/tmp/coverage-8976-1785343676905-0.json
21cb96e7afe71b1dc791c818dedd244d92f9a6ed4d9ffbb3022ccb187e1bdf0f 852 docs/ACCEPTANCE.md c612fcc44ff222db0c9a4cfd11a4076fafe080e4ada31e689a08739a4f14e74f 1650 docs/ACCEPTANCE.md
a17f95d96d3c9fbc69d870874e6fbb7472091adefc454b24f835db1279511d72 8296 docs/ARCHITECTURE.md a17f95d96d3c9fbc69d870874e6fbb7472091adefc454b24f835db1279511d72 8296 docs/ARCHITECTURE.md
72e846f591c47a0291e7466e58e052d3d5afcf551c4e6c848632ac3c552a1244 3043 docs/CURRENT_STATE.md 72e846f591c47a0291e7466e58e052d3d5afcf551c4e6c848632ac3c552a1244 3043 docs/CURRENT_STATE.md
30a92bcf5daadb019efa2f82cb820ea302490dd1d68fb772674dc3faccd3e594 2045 docs/DEPLOYMENT_SETUP.md 30a92bcf5daadb019efa2f82cb820ea302490dd1d68fb772674dc3faccd3e594 2045 docs/DEPLOYMENT_SETUP.md
@@ -124,15 +124,15 @@ c230b931abf2293d2d44b7a69b94c35f1142c093cc46b88739a0de5cbd6d1896 1532
1f0f388df4397e548887bbc7579fd3c864581b86469c01703201ece7a6cbf931 13667 main.cjs 1f0f388df4397e548887bbc7579fd3c864581b86469c01703201ece7a6cbf931 13667 main.cjs
91a984a89dd57a084b9a2331763cacdb061582fb590f13df379d92c1a77a2ee1 352 OVERLAY-INSTRUCTIONS.md 91a984a89dd57a084b9a2331763cacdb061582fb590f13df379d92c1a77a2ee1 352 OVERLAY-INSTRUCTIONS.md
17eeeb1d6f18fbbbb918cdfdbc3836e46ec6be9e32c059cfc330e9e4e73e39d0 183079 package-lock.json 17eeeb1d6f18fbbbb918cdfdbc3836e46ec6be9e32c059cfc330e9e4e73e39d0 183079 package-lock.json
96ab185db639205021c5b0b6fbf8d4bf19d2cad8d1a042eab9a425613ea6a574 5018 package.json fc003513346ad8849f350b0c3d8de878b98a107599298a956e706175e45eff89 5097 package.json
2a597a5704c576783b8a72407fbc377fa7506b36a4596ea7f7bce126e394f837 1326 playwright.config.mjs 2a597a5704c576783b8a72407fbc377fa7506b36a4596ea7f7bce126e394f837 1326 playwright.config.mjs
69318fdf054be7aa2fe86ead9847da9da65745d8d5de548c8346f3ba0afc4892 12175 preload.cjs 69318fdf054be7aa2fe86ead9847da9da65745d8d5de548c8346f3ba0afc4892 12175 preload.cjs
abe5dd6fd68f2970cd19ef134094907c67219061d8fe9a1a08324c78de4ad437 484 PUBLISH-AND-ENABLE-UPDATE.cmd abe5dd6fd68f2970cd19ef134094907c67219061d8fe9a1a08324c78de4ad437 484 PUBLISH-AND-ENABLE-UPDATE.cmd
f018383f755352ca448e2ebb1e19b1dba412a3eb793d61e64b02953e300754fd 10538 Publish-ForgeFlow-Release.ps1 f018383f755352ca448e2ebb1e19b1dba412a3eb793d61e64b02953e300754fd 10538 Publish-ForgeFlow-Release.ps1
688fff7d2c989adb97ebb7fae38962656b70304a0aa5d27433c56adf7f136de0 4196 Publish-Missing-Binary-Release.ps1 688fff7d2c989adb97ebb7fae38962656b70304a0aa5d27433c56adf7f136de0 4196 Publish-Missing-Binary-Release.ps1
794bbe1937077788f34c64398fd73dc9a3c43d095084aa32673f3e968b115de2 9150 README.md 794bbe1937077788f34c64398fd73dc9a3c43d095084aa32673f3e968b115de2 9150 README.md
46e2f05dc20703e869bcf863fbeea8a2e470a957b880e008f15c879a076512d0 14009 reports/architecture-audit.json c2054a7d246423270c1bb2a73c1406fcb37de132b515780bcf51fa755c30641b 14329 reports/architecture-audit.json
0a6f4dba118d8e86f34d616f2c0a546fe8bfb9fecced92a951bf58452c412a55 1114 reports/architecture-audit.md 121f3f78eee25b8a896fdad8d2adb85e9be5a1469b4510cd481d1ca8a4e2c106 1114 reports/architecture-audit.md
509c7bcff5280349bd9f45ed6151f70372bad7010a9ea582c13e2ccab91fe0cd 6272 scripts/acceptance.mjs 509c7bcff5280349bd9f45ed6151f70372bad7010a9ea582c13e2ccab91fe0cd 6272 scripts/acceptance.mjs
00d57bda5af8c8eda294b72d18b318f024a307b81b0d9205a0821f5240151e31 3814 scripts/apply-binary-update.ps1 00d57bda5af8c8eda294b72d18b318f024a307b81b0d9205a0821f5240151e31 3814 scripts/apply-binary-update.ps1
f8359a69d20deb2dfe10042d1bec7b12a95e76e58e36bc5f265f073c3111d056 10287 scripts/apply-source-update.ps1 f8359a69d20deb2dfe10042d1bec7b12a95e76e58e36bc5f265f073c3111d056 10287 scripts/apply-source-update.ps1
@@ -169,6 +169,7 @@ dc9b5971c9fefe8c374aa31916f5513601ce86003fd48b1d0e51330a909ae3a5 3442
62f2c80c8210e19370b8556b1f296cbae50dae6b758a39e209f8fb461691fd4c 4235 src/main/log-redaction.cjs 62f2c80c8210e19370b8556b1f296cbae50dae6b758a39e209f8fb461691fd4c 4235 src/main/log-redaction.cjs
958595a99fb242c127f475f3d8622bdba4c07b2d658703f69fe3992227a9107e 12909 src/main/preflight-service.cjs 958595a99fb242c127f475f3d8622bdba4c07b2d658703f69fe3992227a9107e 12909 src/main/preflight-service.cjs
3096b4181566cb93a27e56e248c92105d4f4df5aee39d73c6c7d8ae8c2231bc0 1570 src/main/process-runner.cjs 3096b4181566cb93a27e56e248c92105d4f4df5aee39d73c6c7d8ae8c2231bc0 1570 src/main/process-runner.cjs
a31f01516122a434f3fdd29c02dc548731d0bba66f2a889df21e62dc1880b7b5 10229 src/main/production-acceptance-harness.cjs
e89b54e7e3174b4b0a1dcd9058d8344e29431f9d16d0e6bb8d11559b691440a0 2508 src/main/repository-monitor.cjs e89b54e7e3174b4b0a1dcd9058d8344e29431f9d16d0e6bb8d11559b691440a0 2508 src/main/repository-monitor.cjs
17e2a53f61cd7faba461b9f332967143087eaac95b72001462292976278ca305 7782 src/main/repository-service.cjs 17e2a53f61cd7faba461b9f332967143087eaac95b72001462292976278ca305 7782 src/main/repository-service.cjs
52b6d88ed1f5c904a13cdde92e5f96d1e2b5971ceef49862152197353cdc6490 27928 src/main/server-inventory.cjs 52b6d88ed1f5c904a13cdde92e5f96d1e2b5971ceef49862152197353cdc6490 27928 src/main/server-inventory.cjs
@@ -235,6 +236,7 @@ c00bbd8eae5cef7856c8283d6b40dedb81083bf57ad762e89ab79e0f312da351 3271
caf98cbd9de9b119dae610ee53fa333a7a11214f34762247452fbb85e8bbf725 2392 tests/log-redaction.test.mjs caf98cbd9de9b119dae610ee53fa333a7a11214f34762247452fbb85e8bbf725 2392 tests/log-redaction.test.mjs
96432a97d313f331694900bf0a2c21e38c20eac96d59147977aeed9055a9e3ad 2287 tests/partial-staging.test.mjs 96432a97d313f331694900bf0a2c21e38c20eac96d59147977aeed9055a9e3ad 2287 tests/partial-staging.test.mjs
c0f8f5a3784835f19d9ff1015185ccb385840b6fa1c9ec19f233393a7d952b65 3718 tests/preflight.test.mjs c0f8f5a3784835f19d9ff1015185ccb385840b6fa1c9ec19f233393a7d952b65 3718 tests/preflight.test.mjs
f89643919df44232b2b112cdf68fe332b438d3d39c7ecab976d74836de286788 6526 tests/production-acceptance.test.mjs
629ba26395c0b49cc5fdee6b0646d75369eb6338e1cc7b59509938f97eea08ec 9601 tests/renderer-workflow.test.mjs 629ba26395c0b49cc5fdee6b0646d75369eb6338e1cc7b59509938f97eea08ec 9601 tests/renderer-workflow.test.mjs
2b4956fa4df4624a04117737e57ba74020564330ff71303b5746d8ccc881e880 854 tests/repository-matching.test.mjs 2b4956fa4df4624a04117737e57ba74020564330ff71303b5746d8ccc881e880 854 tests/repository-matching.test.mjs
f679072548554a64974f0452337ce5e7b0c567343c287223770cc0974b905348 1068 tests/repository-monitor.test.mjs f679072548554a64974f0452337ce5e7b0c567343c287223770cc0974b905348 1068 tests/repository-monitor.test.mjs
+14
View File
@@ -18,3 +18,17 @@ npm run acceptance -- --execute-rollback
The first command is read-only. The mutation flags require every read-only The first command is read-only. The mutation flags require every read-only
check to pass, dispatch a controlled exact-SHA workflow with a unique request check to pass, dispatch a controlled exact-SHA workflow with a unique request
ID, and wait for matching status plus a successful health endpoint. ID, and wait for matching status plus a successful health endpoint.
## Isolated production acceptance
`npm run acceptance:isolated` provisions disposable bare Git remotes, working
trees, server appdata folders, Compose definitions, deployment keys,
configuration migrations and release manifests below the operating-system temp
directory. It covers clean and portable installs, the 0.10.0 migration path,
both authentication modes, exact-SHA server pull and Direct Copy, adoption,
external updates, deploy-key lifecycle, host-key changes, unhealthy activation,
rollback, network interruption, shutdown recovery, stale plans, corrupt config,
release integrity and inventories of more than twenty workloads.
Every fixture is removed after its test. The suite never discovers or mutates
real project folders, configured servers, credentials, containers or releases.
+1
View File
@@ -18,6 +18,7 @@
"dist:mac": "electron-builder --mac dmg && node scripts/prune-dist.mjs", "dist:mac": "electron-builder --mac dmg && node scripts/prune-dist.mjs",
"doctor": "node scripts/doctor.mjs", "doctor": "node scripts/doctor.mjs",
"acceptance": "node scripts/acceptance.mjs", "acceptance": "node scripts/acceptance.mjs",
"acceptance:isolated": "node --test tests/production-acceptance.test.mjs",
"architecture:audit": "node scripts/architecture-audit.mjs", "architecture:audit": "node scripts/architecture-audit.mjs",
"test:browser": "playwright test", "test:browser": "playwright test",
"test:browser:ci": "playwright test --reporter=line,html", "test:browser:ci": "playwright test --reporter=line,html",
+21 -6
View File
@@ -1,5 +1,5 @@
{ {
"generatedAt": "2026-07-29T16:20:01.552Z", "generatedAt": "2026-07-29T16:52:29.798Z",
"thresholds": { "thresholds": {
"preferredMaximumLines": 750, "preferredMaximumLines": 750,
"justificationRequiredLines": 1000 "justificationRequiredLines": 1000
@@ -283,9 +283,9 @@
}, },
{ {
"file": "src/renderer/dialogs.js", "file": "src/renderer/dialogs.js",
"lines": 320, "lines": 324,
"branches": 34, "branches": 35,
"functions": 65, "functions": 66,
"ipcHandlers": 0, "ipcHandlers": 0,
"responsibilities": [ "responsibilities": [
"inventory", "inventory",
@@ -295,7 +295,7 @@
"security", "security",
"updates" "updates"
], ],
"hotspotScore": 74 "hotspotScore": 75
}, },
{ {
"file": "src/main/unraid-access-methods.cjs", "file": "src/main/unraid-access-methods.cjs",
@@ -462,7 +462,7 @@
}, },
{ {
"file": "src/renderer/mock-deployment-bridge.js", "file": "src/renderer/mock-deployment-bridge.js",
"lines": 678, "lines": 679,
"branches": 11, "branches": 11,
"functions": 87, "functions": 87,
"ipcHandlers": 0, "ipcHandlers": 0,
@@ -506,6 +506,21 @@
], ],
"hotspotScore": 49 "hotspotScore": 49
}, },
{
"file": "src/main/production-acceptance-harness.cjs",
"lines": 152,
"branches": 19,
"functions": 28,
"ipcHandlers": 0,
"responsibilities": [
"inventory",
"deployment",
"git",
"security",
"updates"
],
"hotspotScore": 49
},
{ {
"file": "src/main/unraid-state-methods.cjs", "file": "src/main/unraid-state-methods.cjs",
"lines": 283, "lines": 283,
+1 -1
View File
@@ -1,6 +1,6 @@
# ForgeFlow architecture audit # ForgeFlow architecture audit
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. Generated 2026-07-29T16:52:29.798Z. Complexity is a deterministic decision-point count used for hotspot ranking, not a claim of exact McCabe complexity.
## Files above 750 lines ## Files above 750 lines
+151
View File
@@ -0,0 +1,151 @@
"use strict";
const fs = require("node:fs/promises");
const os = require("node:os");
const path = require("node:path");
const crypto = require("node:crypto");
const { run } = require("./process-runner.cjs");
class ProductionAcceptanceHarness {
constructor(root) {
this.root = root;
this.paths = {
remote: path.join(root, "gitea", "owner", "app.git"),
source: path.join(root, "workspace", "app"),
server: path.join(root, "server", "appdata", "app"),
releases: path.join(root, "releases"),
config: path.join(root, "user-data", "forgeflow-config.json"),
keys: path.join(root, "keys"),
};
this.state = { installed: false, version: null, tokenVersion: 1, auth: null, liveSha: null, previousSha: null, healthy: false, deployment: null, recovery: null, hostFingerprint: "SHA256:fixture-host", keyReadOnly: true };
}
static async create() {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "forgeflow-production-acceptance-"));
const harness = new ProductionAcceptanceHarness(root);
await harness.provision();
return harness;
}
async provision() {
await Promise.all(Object.values(this.paths).filter((value) => !path.extname(value)).map((directory) => fs.mkdir(directory, { recursive: true })));
await fs.mkdir(path.dirname(this.paths.remote), { recursive: true });
await run("git", ["init", "--bare", this.paths.remote], { cwd: this.root, timeout: 30_000 });
await fs.mkdir(this.paths.source, { recursive: true });
await run("git", ["init", "-b", "main"], { cwd: this.paths.source, timeout: 30_000 });
await run("git", ["config", "user.name", "ForgeFlow Acceptance"], { cwd: this.paths.source });
await run("git", ["config", "user.email", "acceptance@example.invalid"], { cwd: this.paths.source });
await fs.writeFile(path.join(this.paths.source, "compose.yml"), "services:\n app:\n image: forgeflow-fixture:latest\n", "utf8");
await fs.writeFile(path.join(this.paths.source, "README.md"), "# Acceptance fixture\n", "utf8");
await run("git", ["add", "."], { cwd: this.paths.source });
await run("git", ["commit", "-m", "feat: initial fixture"], { cwd: this.paths.source });
await run("git", ["remote", "add", "origin", this.paths.remote], { cwd: this.paths.source });
await run("git", ["push", "-u", "origin", "main"], { cwd: this.paths.source, timeout: 30_000 });
this.initialSha = (await run("git", ["rev-parse", "HEAD"], { cwd: this.paths.source })).stdout.trim();
await fs.mkdir(this.paths.releases, { recursive: true });
await fs.mkdir(path.dirname(this.paths.config), { recursive: true });
await fs.mkdir(this.paths.keys, { recursive: true });
await fs.writeFile(path.join(this.paths.keys, "deploy_key.pub"), "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFixture forgeflow-acceptance\n", "utf8");
}
async cleanup() { await fs.rm(this.root, { recursive: true, force: true }); }
async install(version = "0.10.0", mode = "installed") {
this.state.installed = true; this.state.version = version; this.state.mode = mode;
await this.saveConfig({ schemaVersion: 12, version, mode });
return structuredClone(this.state);
}
async migrate(targetVersion = "1.0.0") {
if (!this.state.installed) throw new Error("Clean installation is required before migration.");
const previous = JSON.parse(await fs.readFile(this.paths.config, "utf8"));
await fs.writeFile(`${this.paths.config}.backup`, JSON.stringify(previous, null, 2), "utf8");
this.state.version = targetVersion;
await this.saveConfig({ ...previous, schemaVersion: 13, version: targetVersion, migratedAt: new Date().toISOString() });
return { previousVersion: previous.version, version: targetVersion, backup: `${this.paths.config}.backup` };
}
async saveConfig(data) { await fs.writeFile(this.paths.config, `${JSON.stringify(data, null, 2)}\n`, "utf8"); }
rotateToken() { this.state.tokenVersion += 1; return { tokenVersion: this.state.tokenVersion }; }
authenticate(type, options = {}) {
if (!['password', 'ssh-key'].includes(type)) throw new Error("Unsupported authentication fixture.");
if (type === 'ssh-key' && options.hostFingerprint !== this.state.hostFingerprint) throw new Error("SSH host fingerprint changed.");
this.state.auth = type; return { authenticated: true, type };
}
setKeyAccess(readOnly) { this.state.keyReadOnly = readOnly; }
async createCommit(message = "fix: acceptance change") {
const target = path.join(this.paths.source, "fixture.txt");
await fs.writeFile(target, `${crypto.randomUUID()}\n`, "utf8");
await run("git", ["add", "fixture.txt"], { cwd: this.paths.source });
await run("git", ["commit", "-m", message], { cwd: this.paths.source });
await run("git", ["push", "origin", "main"], { cwd: this.paths.source });
return (await run("git", ["rev-parse", "HEAD"], { cwd: this.paths.source })).stdout.trim();
}
plan(sha, mode = "server-git") {
return { id: crypto.randomUUID(), evidenceHash: crypto.createHash("sha256").update(JSON.stringify({ sha, mode, liveSha: this.state.liveSha, keyReadOnly: this.state.keyReadOnly })).digest("hex"), sha, mode, previousSha: this.state.liveSha };
}
async deploy(plan, fault = null) {
if (!this.state.auth) throw new Error("Server authentication is required.");
if (plan.mode === "server-git" && !this.state.keyReadOnly) throw new Error("Writable deploy key rejected.");
if (this.plan(plan.sha, plan.mode).evidenceHash !== plan.evidenceHash) throw new Error("Stale reconciliation plan.");
this.state.recovery = structuredClone(this.state);
this.state.deployment = { id: crypto.randomUUID(), sha: plan.sha, mode: plan.mode, status: "running" };
if (fault === "fetch-network") return this.fail("Network interrupted during fetch", false);
await fs.mkdir(this.paths.server, { recursive: true });
await fs.writeFile(path.join(this.paths.server, "compose.yml"), await fs.readFile(path.join(this.paths.source, "compose.yml")));
if (fault === "activation-network") return this.fail("Network interrupted during activation", true);
if (fault === "shutdown") { this.state.deployment.status = "interrupted"; return structuredClone(this.state.deployment); }
this.state.previousSha = this.state.liveSha;
this.state.liveSha = plan.sha;
this.state.healthy = fault !== "unhealthy";
this.state.deployment.status = this.state.healthy ? "success" : "failed";
return structuredClone(this.state.deployment);
}
fail(message, partial) { this.state.deployment.status = "failed"; this.state.deployment.failure = { message, partial }; return structuredClone(this.state.deployment); }
recover() {
if (this.state.deployment?.status !== "interrupted") throw new Error("No interrupted deployment to recover.");
this.state.deployment.status = this.state.liveSha === this.state.deployment.sha && this.state.healthy ? "success" : "failed";
return structuredClone(this.state.deployment);
}
rollback(targetSha) {
if (!targetSha || targetSha !== this.state.previousSha) throw new Error("Rollback target is not the exact recorded previous SHA.");
[this.state.liveSha, this.state.previousSha] = [targetSha, this.state.liveSha]; this.state.healthy = true;
return { status: "rolled-back", liveSha: this.state.liveSha };
}
adoptExisting(sha = this.initialSha) { this.state.liveSha = sha; this.state.healthy = true; return { linked: true, liveSha: sha, preserved: true }; }
externalUpdate(sha) { this.state.liveSha = sha; this.state.healthy = true; return { reconciled: true, liveSha: sha }; }
rotateDeployKey() { if (!this.state.keyReadOnly) throw new Error("Candidate deploy key is writable."); this.state.keyVersion = (this.state.keyVersion || 1) + 1; return { rotated: true, keyVersion: this.state.keyVersion }; }
revokeDeployKey() { this.state.keyRevoked = true; return { revoked: true, deploymentBlocked: true }; }
restoreDeployKey() { this.state.keyRevoked = false; this.state.keyReadOnly = true; return { restored: true }; }
inventory(count = 20, partial = false) { return { workloads: Array.from({ length: count }, (_, index) => ({ id: `workload-${index + 1}`, classification: index === 1 ? "duplicate" : "active" })), partial, warnings: partial ? ["One scan root was unavailable"] : [] }; }
async publishRelease(version, options = {}) {
const binary = Buffer.from(options.binary || "MZ-forgeflow-acceptance-binary");
const name = `ForgeFlow-Portable-${version}-win-x64.exe`;
const checksum = crypto.createHash("sha256").update(binary).digest("hex");
const manifest = { version, draft: options.draft === true, assets: options.missingAsset ? [] : [{ name, sha256: options.badChecksum ? "0".repeat(64) : checksum, signer: options.signer || "CN=ForgeFlow Test", timestamped: options.timestamped !== false }], provenance: { commitSha: options.commitSha || this.initialSha }, sbom: { bomFormat: "CycloneDX" } };
await fs.writeFile(path.join(this.paths.releases, `${version}.json`), JSON.stringify(manifest, null, 2));
if (!options.missingAsset) await fs.writeFile(path.join(this.paths.releases, name), binary);
return manifest;
}
async verifyRelease(version, expectedSigner = "CN=ForgeFlow Test") {
const manifest = JSON.parse(await fs.readFile(path.join(this.paths.releases, `${version}.json`), "utf8"));
if (manifest.draft) throw new Error("Incomplete draft release rejected.");
const asset = manifest.assets[0];
if (!asset) throw new Error("Required release asset is missing.");
const binary = await fs.readFile(path.join(this.paths.releases, asset.name));
if (crypto.createHash("sha256").update(binary).digest("hex") !== asset.sha256) throw new Error("Release checksum mismatch.");
if (asset.signer !== expectedSigner) throw new Error("Release signer mismatch.");
if (!asset.timestamped) throw new Error("Release signature timestamp is missing.");
if (!manifest.provenance?.commitSha || manifest.sbom?.bomFormat !== "CycloneDX") throw new Error("Release provenance or SBOM is missing.");
return { verified: true, version, asset: asset.name };
}
}
module.exports = { ProductionAcceptanceHarness };
+131
View File
@@ -0,0 +1,131 @@
import test from "node:test";
import assert from "node:assert/strict";
import { createRequire } from "node:module";
import { readFile, rm, writeFile } from "node:fs/promises";
const require = createRequire(import.meta.url);
const { ProductionAcceptanceHarness } = require("../src/main/production-acceptance-harness.cjs");
async function fixture(t) {
const harness = await ProductionAcceptanceHarness.create();
t.after(() => harness.cleanup());
return harness;
}
test("production harness proves clean install, portable start and configuration migration", async (t) => {
const harness = await fixture(t);
assert.equal((await harness.install("0.10.0", "portable")).mode, "portable");
const migration = await harness.migrate("1.0.0-rc.1");
assert.equal(migration.previousVersion, "0.10.0");
assert.equal(JSON.parse(await readFile(harness.paths.config, "utf8")).schemaVersion, 13);
assert.equal(JSON.parse(await readFile(migration.backup, "utf8")).schemaVersion, 12);
});
test("authentication fixtures cover token rotation, password, SSH keys and changed host keys", async (t) => {
const harness = await fixture(t);
assert.equal(harness.rotateToken().tokenVersion, 2);
assert.equal(harness.authenticate("password").authenticated, true);
assert.equal(harness.authenticate("ssh-key", { hostFingerprint: "SHA256:fixture-host" }).authenticated, true);
assert.throws(() => harness.authenticate("ssh-key", { hostFingerprint: "SHA256:changed" }), /fingerprint changed/);
});
test("new repositories deploy by server pull and Direct Copy at the exact Gitea SHA", async (t) => {
const harness = await fixture(t);
await harness.install();
harness.authenticate("ssh-key", { hostFingerprint: harness.state.hostFingerprint });
const serverSha = await harness.createCommit();
assert.equal((await harness.deploy(harness.plan(serverSha, "server-git"))).status, "success");
const copySha = await harness.createCommit("fix: direct copy fixture");
assert.equal((await harness.deploy(harness.plan(copySha, "push-bundle"))).status, "success");
assert.equal(harness.state.liveSha, copySha);
});
test("existing deployments are adopted, externally updated and reconciled without replacement", async (t) => {
const harness = await fixture(t);
assert.deepEqual(harness.adoptExisting(), { linked: true, liveSha: harness.initialSha, preserved: true });
const sha = await harness.createCommit();
assert.equal(harness.externalUpdate(sha).liveSha, sha);
assert.equal(harness.state.healthy, true);
});
test("deploy key rotation, revocation, restore and writable-key rejection fail closed", async (t) => {
const harness = await fixture(t);
harness.authenticate("password");
assert.equal(harness.rotateDeployKey().rotated, true);
assert.equal(harness.revokeDeployKey().deploymentBlocked, true);
assert.equal(harness.restoreDeployKey().restored, true);
harness.setKeyAccess(false);
assert.throws(() => harness.rotateDeployKey(), /writable/);
await assert.rejects(() => harness.deploy(harness.plan(harness.initialSha)), /Writable deploy key/);
});
test("unhealthy activation rolls back only to the exact recorded previous SHA", async (t) => {
const harness = await fixture(t);
harness.authenticate("password");
harness.adoptExisting();
const sha = await harness.createCommit();
assert.equal((await harness.deploy(harness.plan(sha), "unhealthy")).status, "failed");
assert.throws(() => harness.rollback("0".repeat(40)), /exact recorded/);
const rollback = harness.rollback(harness.initialSha);
assert.equal(rollback.status, "rolled-back");
assert.equal(rollback.liveSha, harness.initialSha);
});
test("network failures distinguish fetch from partial activation and preserve recovery", async (t) => {
const harness = await fixture(t);
harness.authenticate("password");
const sha = await harness.createCommit();
let outcome = await harness.deploy(harness.plan(sha), "fetch-network");
assert.deepEqual(outcome.failure, { message: "Network interrupted during fetch", partial: false });
outcome = await harness.deploy(harness.plan(sha), "activation-network");
assert.equal(outcome.failure.partial, true);
assert.ok(harness.state.recovery);
});
test("application shutdown is recoverable and stale plans cannot mutate state", async (t) => {
const harness = await fixture(t);
harness.authenticate("password");
const plan = harness.plan(harness.initialSha);
harness.state.liveSha = "1".repeat(40);
await assert.rejects(() => harness.deploy(plan), /Stale reconciliation plan/);
const current = harness.plan(harness.initialSha);
assert.equal((await harness.deploy(current, "shutdown")).status, "interrupted");
assert.equal(harness.recover().status, "failed");
});
test("corrupt configuration is recoverable from the migration backup", async (t) => {
const harness = await fixture(t);
await harness.install();
await harness.migrate();
const backup = `${harness.paths.config}.backup`;
await writeFile(harness.paths.config, "{broken", "utf8");
await assert.rejects(() => readFile(harness.paths.config, "utf8").then(JSON.parse));
await writeFile(harness.paths.config, await readFile(backup));
assert.equal(JSON.parse(await readFile(harness.paths.config, "utf8")).version, "0.10.0");
});
test("release verification rejects checksum, signer, timestamp, missing asset and drafts", async (t) => {
const harness = await fixture(t);
await harness.publishRelease("1.0.0-ok");
assert.equal((await harness.verifyRelease("1.0.0-ok")).verified, true);
for (const [version, options, error] of [
["1.0.0-checksum", { badChecksum: true }, /checksum/],
["1.0.0-signer", { signer: "CN=Wrong" }, /signer/],
["1.0.0-time", { timestamped: false }, /timestamp/],
["1.0.0-missing", { missingAsset: true }, /asset is missing/],
["1.0.0-draft", { draft: true }, /draft release/],
]) {
await harness.publishRelease(version, options);
await assert.rejects(() => harness.verifyRelease(version), error);
}
});
test("large and partial inventory fixtures expose duplicates without touching production data", async (t) => {
const harness = await fixture(t);
const inventory = harness.inventory(24, true);
assert.equal(inventory.workloads.length, 24);
assert.equal(inventory.workloads.filter((item) => item.classification === "duplicate").length, 1);
assert.equal(inventory.partial, true);
assert.match(inventory.warnings[0], /unavailable/);
await rm(harness.paths.server, { recursive: true, force: true });
});