fix: restore scrolling and validator enforcement
ForgeFlow quality gate / quality (push) Canceled after 0s
ForgeFlow quality gate / quality (push) Canceled after 0s
This commit is contained in:
+8
-8
@@ -131,15 +131,15 @@ dd613d04b366f2cd071a1685a414016a5fb008082ed1b4cb8b24b79c100f640a 2412
|
|||||||
c157640e76d558906a9aa9881eda811196623ef1c65fa3467f32f0f84b0ddd0c 15095 src/main/diagnostics-service.cjs
|
c157640e76d558906a9aa9881eda811196623ef1c65fa3467f32f0f84b0ddd0c 15095 src/main/diagnostics-service.cjs
|
||||||
a2ef47d5330095b92c2bd22fcc39962091881f9cb60d02e261eb1dd1bd693170 1974 src/main/external-tools-service.cjs
|
a2ef47d5330095b92c2bd22fcc39962091881f9cb60d02e261eb1dd1bd693170 1974 src/main/external-tools-service.cjs
|
||||||
0b7476c2cfe1872601978c20a466c20fe58be35e81b2303e38a753fea62bbc27 32548 src/main/git-service.cjs
|
0b7476c2cfe1872601978c20a466c20fe58be35e81b2303e38a753fea62bbc27 32548 src/main/git-service.cjs
|
||||||
b23dfa041d9f4597d144601ab8569e69ba748a0d7a075c3ef01154eacc2640ae 7018 src/main/git-validator-policy.cjs
|
e28fc1ca2fd4c0116148f5005d793feddf04c36ef711d2d348560394d209a613 7253 src/main/git-validator-policy.cjs
|
||||||
ca050e9820528b555e6a1dfd89ac8be2f9a0cec6e91254897e6e3b6e116a7eb2 26317 src/main/git-validator-service.cjs
|
3a101b63ad3761c26350c2ac0793279a0b27672b91a5b1d8dc75bc44d92f0b52 27128 src/main/git-validator-service.cjs
|
||||||
3cc53e24e023aa0d8bf36c35ce9672ca98e6c74066512c8b59ab42274e838c22 21307 src/main/gitea-service.cjs
|
3cc53e24e023aa0d8bf36c35ce9672ca98e6c74066512c8b59ab42274e838c22 21307 src/main/gitea-service.cjs
|
||||||
2ad3b2e647377f687ad987fe248a142ad399ecac98e4b49965aa7efc6093e5fa 6914 src/main/inventory-classifier.cjs
|
2ad3b2e647377f687ad987fe248a142ad399ecac98e4b49965aa7efc6093e5fa 6914 src/main/inventory-classifier.cjs
|
||||||
dafdb09133d2b6ec2161a3f0b09354551e54fc606c8107976fca37405643be91 3404 src/main/inventory-review-service.cjs
|
dafdb09133d2b6ec2161a3f0b09354551e54fc606c8107976fca37405643be91 3404 src/main/inventory-review-service.cjs
|
||||||
00989577aed509a7ddfdf9f4df09a196a393a85b5ea59b21089002215e69f065 25949 src/main/ipc.cjs
|
00989577aed509a7ddfdf9f4df09a196a393a85b5ea59b21089002215e69f065 25949 src/main/ipc.cjs
|
||||||
0eb1cfdcd3a37a0ec9502bf753966f87230c03580335798bef9265add42ee6fa 12530 src/main/ipc/deployment-handlers.cjs
|
0eb1cfdcd3a37a0ec9502bf753966f87230c03580335798bef9265add42ee6fa 12530 src/main/ipc/deployment-handlers.cjs
|
||||||
dc9b5971c9fefe8c374aa31916f5513601ce86003fd48b1d0e51330a909ae3a5 3442 src/main/ipc/operations-handlers.cjs
|
dc9b5971c9fefe8c374aa31916f5513601ce86003fd48b1d0e51330a909ae3a5 3442 src/main/ipc/operations-handlers.cjs
|
||||||
2e2d0b26480b395906b7881e50b596c9a7701e6e534497d04d541bf3f3b3c057 15673 src/main/ipc/repository-handlers.cjs
|
629b0f4704cda6ed6288c5a6ec9e5d73d6ae3e2692c43581b258431b65b58e51 15887 src/main/ipc/repository-handlers.cjs
|
||||||
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
|
||||||
@@ -173,10 +173,10 @@ cdfaacdcd5ae04b0e5c79fefa21f5e09d5c810bcea504c5b6e1d6b744182ff84 15567
|
|||||||
977af9585074f0a404830c5a93de6939b4897d2eb98ba8ae75c3cf0023dea673 5782 src/renderer/events.js
|
977af9585074f0a404830c5a93de6939b4897d2eb98ba8ae75c3cf0023dea673 5782 src/renderer/events.js
|
||||||
a84da5aecbb16ce7983dba1f6d6aab1bf47b2e9a87c2933fa1afb8123f7ef7d6 1497 src/renderer/index.html
|
a84da5aecbb16ce7983dba1f6d6aab1bf47b2e9a87c2933fa1afb8123f7ef7d6 1497 src/renderer/index.html
|
||||||
06180d9656dd254edfb6949c397f8e313954fc560ddcb22b3a35fce3c3e35655 21350 src/renderer/mock-bridge.js
|
06180d9656dd254edfb6949c397f8e313954fc560ddcb22b3a35fce3c3e35655 21350 src/renderer/mock-bridge.js
|
||||||
4b0a64610da0c446f15a43e4753b26f29de72e07793e2fa7b7322d4f07cf8050 27806 src/renderer/mock-deployment-bridge.js
|
9fa522dad0088e2981c4ca5c392e93d6c92e04ff23fd1083d9040a3aa52d2b55 28101 src/renderer/mock-deployment-bridge.js
|
||||||
cbb21f5f4b299f24d6cea9070fafcd14315d3ad6259bc8e11c0e93350084fff5 20032 src/renderer/mock-repository-bridge.js
|
cbb21f5f4b299f24d6cea9070fafcd14315d3ad6259bc8e11c0e93350084fff5 20032 src/renderer/mock-repository-bridge.js
|
||||||
94fa265c2fe9ca8d644f0ce9b620b6f85d9b25dca5802c4e9195b66dcbe80120 6522 src/renderer/operations.js
|
94fa265c2fe9ca8d644f0ce9b620b6f85d9b25dca5802c4e9195b66dcbe80120 6522 src/renderer/operations.js
|
||||||
abe196f5ecdd73e7b6ca67a41c90e55bfc507e084f786227264cc780b1ce83a3 78001 src/renderer/styles.css
|
6776e0adb690b8f36274bfc5b31e3140054893c46ba64621c4f56a0d91b52fc2 78323 src/renderer/styles.css
|
||||||
1703e64533b7e2717b27c5776296c7dd76331e6f97e8005aea9fd688f1aee3ae 94834 src/renderer/views.js
|
1703e64533b7e2717b27c5776296c7dd76331e6f97e8005aea9fd688f1aee3ae 94834 src/renderer/views.js
|
||||||
0a1e9d9d6cd4d190eb7f85dbc6668d80600b1cf2749cc0c2c51cc428f506f20d 1121 src/shared/clone-target.cjs
|
0a1e9d9d6cd4d190eb7f85dbc6668d80600b1cf2749cc0c2c51cc428f506f20d 1121 src/shared/clone-target.cjs
|
||||||
5d425d5c2f939d0f6beebee7ebb0c77146cb7e318535ba7286ec7081a4dc2269 2497 src/shared/deployment-policy.cjs
|
5d425d5c2f939d0f6beebee7ebb0c77146cb7e318535ba7286ec7081a4dc2269 2497 src/shared/deployment-policy.cjs
|
||||||
@@ -191,7 +191,7 @@ f8853dce6fdf360d5df2fbe2b6df3e5687630c807fee5ba8436679b34ec737ea 2436
|
|||||||
058aeaa5d9bfe377c7e322f213c7871ecc4151b5d08ef790992f4ee28d857658 743 START-FORGEFLOW-OVERLAY.ps1
|
058aeaa5d9bfe377c7e322f213c7871ecc4151b5d08ef790992f4ee28d857658 743 START-FORGEFLOW-OVERLAY.ps1
|
||||||
f5b0ea887fcdeadec78c1ad49b0ec7979723562f5c0b730703acb77a37281ee0 1009 tests/acceptance.test.mjs
|
f5b0ea887fcdeadec78c1ad49b0ec7979723562f5c0b730703acb77a37281ee0 1009 tests/acceptance.test.mjs
|
||||||
a4e5947204ff6878e601e32477bc85b53cd0153baf95a161c8935b6e5466c257 1155 tests/audit-service.test.mjs
|
a4e5947204ff6878e601e32477bc85b53cd0153baf95a161c8935b6e5466c257 1155 tests/audit-service.test.mjs
|
||||||
33bc892963e89b868235b959498308a456b1285057bda524ba7c1d5a9ee2159e 8763 tests/browser/forgeflow.spec.mjs
|
2d57a66bb2a6461e4c3266f14998d505ba0582135b0bbcf8fce9f1542a70007a 10479 tests/browser/forgeflow.spec.mjs
|
||||||
454edeaccb2bd41043bc918d3e3a6127db14339031d6a1c1562ac855e90455d2 4318 tests/clone-target.test.mjs
|
454edeaccb2bd41043bc918d3e3a6127db14339031d6a1c1562ac855e90455d2 4318 tests/clone-target.test.mjs
|
||||||
ac17f8bbe9e388b80abef7792c8b184a1fd482c93f13d23a478e433961020f75 17214 tests/config-store.test.mjs
|
ac17f8bbe9e388b80abef7792c8b184a1fd482c93f13d23a478e433961020f75 17214 tests/config-store.test.mjs
|
||||||
f1463326aee79842d265687ae628189ce54e92544600f2bd14073780287cfb14 2502 tests/configuration-backup.test.mjs
|
f1463326aee79842d265687ae628189ce54e92544600f2bd14073780287cfb14 2502 tests/configuration-backup.test.mjs
|
||||||
@@ -202,8 +202,8 @@ fae3634bae871abade4d487b94b4741b50e787804dbd6135249f634fdd83c6d0 3800
|
|||||||
dd121d96ca265a027cd415a52064500a4541b2f8a662f4f4b25f2f996d52b5da 762 tests/external-tools.test.mjs
|
dd121d96ca265a027cd415a52064500a4541b2f8a662f4f4b25f2f996d52b5da 762 tests/external-tools.test.mjs
|
||||||
e7aebcc0d484a6a59d463d5cb26c11b3ad56e28f6535e7c38a0fe166a41565ea 13690 tests/git-integration.test.mjs
|
e7aebcc0d484a6a59d463d5cb26c11b3ad56e28f6535e7c38a0fe166a41565ea 13690 tests/git-integration.test.mjs
|
||||||
5ea94c6b241a02060d531fad94e449eecd3772eed2137581d4e2babfb09e56db 1239 tests/git-status.test.mjs
|
5ea94c6b241a02060d531fad94e449eecd3772eed2137581d4e2babfb09e56db 1239 tests/git-status.test.mjs
|
||||||
c00bbd8eae5cef7856c8283d6b40dedb81083bf57ad762e89ab79e0f312da351 3271 tests/git-validator-policy.test.mjs
|
61e0b8cad926acd22b5b17e4044f7edcbe96b6977cbbe2b6fbe123406626fc89 4283 tests/git-validator-policy.test.mjs
|
||||||
73d00729696e5067ba33dd6d43b018d89ce7fdd561a60ab66648d3283fb54d21 5370 tests/git-validator.test.mjs
|
2b31459f14a5e36e30cf84c1054f634f4dba8676d29adeb9c2a8e18179f56fa0 6097 tests/git-validator.test.mjs
|
||||||
681ab7bcd02c4dd98d1d8d2092a3521c489d941131e7ffe5903971b940046474 2403 tests/git-workflows.test.mjs
|
681ab7bcd02c4dd98d1d8d2092a3521c489d941131e7ffe5903971b940046474 2403 tests/git-workflows.test.mjs
|
||||||
d633c59bd910008223c834c6d7f3e5666c685a0881944263ede2d42cc69d3151 18710 tests/gitea-actions.test.mjs
|
d633c59bd910008223c834c6d7f3e5666c685a0881944263ede2d42cc69d3151 18710 tests/gitea-actions.test.mjs
|
||||||
fcc9a063882840dd89d74c2785284c8f2f6a9e5acec482b6d89ed8de62efdb85 9635 tests/inventory-classifier.test.mjs
|
fcc9a063882840dd89d74c2785284c8f2f6a9e5acec482b6d89ed8de62efdb85 9635 tests/inventory-classifier.test.mjs
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ function normalizePolicy(policy = {}) {
|
|||||||
enabledChecks: Array.isArray(custom.enabledChecks) ? [...new Set(custom.enabledChecks.map(String))] : null,
|
enabledChecks: Array.isArray(custom.enabledChecks) ? [...new Set(custom.enabledChecks.map(String))] : null,
|
||||||
severityOverrides: custom.severityOverrides && typeof custom.severityOverrides === "object" ? { ...custom.severityOverrides } : {},
|
severityOverrides: custom.severityOverrides && typeof custom.severityOverrides === "object" ? { ...custom.severityOverrides } : {},
|
||||||
blockingChecks: [...new Set((custom.blockingChecks || policy.blockingChecks || []).map(String))],
|
blockingChecks: [...new Set((custom.blockingChecks || policy.blockingChecks || []).map(String))],
|
||||||
|
blockingSeverities: [...new Set((custom.blockingSeverities || policy.blockingSeverities || base.severities || ["error"]).map(String))]
|
||||||
|
.filter((severity) => ["warning", "error"].includes(severity)),
|
||||||
allowSuppressions: custom.allowSuppressions ?? base.allowSuppressions ?? true,
|
allowSuppressions: custom.allowSuppressions ?? base.allowSuppressions ?? true,
|
||||||
maxSuppressionDays: Math.max(1, Number(custom.maxSuppressionDays ?? base.maxSuppressionDays ?? 30)),
|
maxSuppressionDays: Math.max(1, Number(custom.maxSuppressionDays ?? base.maxSuppressionDays ?? 30)),
|
||||||
};
|
};
|
||||||
@@ -57,7 +59,7 @@ function applyPolicy(checks, policyInput, suppressions = [], now = new Date()) {
|
|||||||
suppressed: Boolean(suppression),
|
suppressed: Boolean(suppression),
|
||||||
suppression: suppression || null,
|
suppression: suppression || null,
|
||||||
expiredSuppression: expiredSuppression || null,
|
expiredSuppression: expiredSuppression || null,
|
||||||
blocking: !suppression && status !== "pass" && (status === "error" || policy.blockingChecks.includes(check.id)),
|
blocking: !suppression && status !== "pass" && (policy.blockingSeverities.includes(status) || policy.blockingChecks.includes(check.id)),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return { policy, checks: relevant };
|
return { policy, checks: relevant };
|
||||||
|
|||||||
@@ -484,12 +484,23 @@ class GitValidatorService {
|
|||||||
throw new Error("Unsupported Git Validator repair action.");
|
throw new Error("Unsupported Git Validator repair action.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async resolveRepairCheck(repository, candidate) {
|
||||||
|
const checkId = String(candidate?.id || candidate?.checkId || "").trim();
|
||||||
|
if (!checkId) throw new Error("A current Git Validator check ID is required.");
|
||||||
|
const report = await this.scan(repository);
|
||||||
|
const current = report.checks.find((check) => check.id === checkId);
|
||||||
|
if (!current?.fixAction)
|
||||||
|
throw new Error("This finding is resolved, suppressed or no longer repairable. Scan again before repairing.");
|
||||||
|
if (candidate?.fixAction && candidate.fixAction !== current.fixAction)
|
||||||
|
throw new Error("The Git Validator repair request is stale. Scan again before repairing.");
|
||||||
|
return current;
|
||||||
|
}
|
||||||
summarize(repository, checks) {
|
summarize(repository, checks) {
|
||||||
const totalWeight = checks.reduce((sum, check) => sum + check.weight, 0);
|
const totalWeight = checks.reduce((sum, check) => sum + check.weight, 0);
|
||||||
const earned = checks.reduce(
|
const earned = checks.reduce(
|
||||||
(sum, check) =>
|
(sum, check) =>
|
||||||
sum +
|
sum +
|
||||||
(check.status === "pass"
|
(check.status === "pass" || check.suppressed
|
||||||
? check.weight
|
? check.weight
|
||||||
: check.status === "warning"
|
: check.status === "warning"
|
||||||
? check.weight * 0.45
|
? check.weight * 0.45
|
||||||
@@ -512,8 +523,9 @@ class GitValidatorService {
|
|||||||
checks,
|
checks,
|
||||||
summary: {
|
summary: {
|
||||||
passed: checks.filter((check) => check.status === "pass").length,
|
passed: checks.filter((check) => check.status === "pass").length,
|
||||||
warnings: checks.filter((check) => check.status === "warning").length,
|
warnings: checks.filter((check) => check.status === "warning" && !check.suppressed).length,
|
||||||
errors: checks.filter((check) => check.status === "error").length,
|
errors: checks.filter((check) => check.status === "error" && !check.suppressed).length,
|
||||||
|
suppressed: checks.filter((check) => check.suppressed).length,
|
||||||
repairable: checks.filter((check) => check.fixAction).length,
|
repairable: checks.filter((check) => check.fixAction).length,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -373,7 +373,8 @@ function registerRepositoryIpc({
|
|||||||
});
|
});
|
||||||
register("git-validator:preview-repair", async ({ fullName, check }) => {
|
register("git-validator:preview-repair", async ({ fullName, check }) => {
|
||||||
const repository = await resolveRepository({ fullName });
|
const repository = await resolveRepository({ fullName });
|
||||||
return gitValidator.previewRepair(repository, check);
|
const currentCheck = await gitValidator.resolveRepairCheck(repository, check);
|
||||||
|
return gitValidator.previewRepair(repository, currentCheck);
|
||||||
});
|
});
|
||||||
register("git-validator:export", async ({ fullName, format = "json" }) => {
|
register("git-validator:export", async ({ fullName, format = "json" }) => {
|
||||||
const repository = await resolveRepository({ fullName });
|
const repository = await resolveRepository({ fullName });
|
||||||
@@ -390,18 +391,19 @@ function registerRepositoryIpc({
|
|||||||
"add-editorconfig",
|
"add-editorconfig",
|
||||||
"protect-default-branch",
|
"protect-default-branch",
|
||||||
]);
|
]);
|
||||||
if (!allowed.has(check?.fixAction))
|
const currentCheck = await gitValidator.resolveRepairCheck(repository, check);
|
||||||
|
if (!allowed.has(currentCheck.fixAction))
|
||||||
throw new Error("Unsupported Git Validator repair request.");
|
throw new Error("Unsupported Git Validator repair request.");
|
||||||
const result = await gitValidator.repair(repository, check);
|
const result = await gitValidator.repair(repository, currentCheck);
|
||||||
await audit.append("git-validator.repair", {
|
await audit.append("git-validator.repair", {
|
||||||
repository: repository.fullName,
|
repository: repository.fullName,
|
||||||
checkId: check.id,
|
checkId: currentCheck.id,
|
||||||
action: check.fixAction,
|
action: currentCheck.fixAction,
|
||||||
});
|
});
|
||||||
await diagnostics.info("git-validator.repair.completed", {
|
await diagnostics.info("git-validator.repair.completed", {
|
||||||
repository: repository.fullName,
|
repository: repository.fullName,
|
||||||
checkId: check.id,
|
checkId: currentCheck.id,
|
||||||
action: check.fixAction,
|
action: currentCheck.fixAction,
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -461,13 +461,15 @@ function createMockDeploymentBridge(context) {
|
|||||||
},
|
},
|
||||||
async gitValidatorScan(fullName) {
|
async gitValidatorScan(fullName) {
|
||||||
await wait(260);
|
await wait(260);
|
||||||
|
const policy = state.gitValidatorPolicy || { id: "standard", label: "Standard", requiredScore: 70 };
|
||||||
|
const activeWarnings = 3;
|
||||||
return {
|
return {
|
||||||
repository: fullName,
|
repository: fullName,
|
||||||
checkedAt: iso(),
|
checkedAt: iso(),
|
||||||
score: 78,
|
score: 78,
|
||||||
grade: "Good",
|
grade: "Good",
|
||||||
policy: state.gitValidatorPolicy || { id: "standard", label: "Standard", requiredScore: 70 },
|
policy,
|
||||||
ready: true,
|
ready: 78 >= policy.requiredScore && (policy.id === "minimal" || activeWarnings === 0),
|
||||||
commitSha: "8cbaf303aa3bb9b4023a7c89aa13fb70ce612847",
|
commitSha: "8cbaf303aa3bb9b4023a7c89aa13fb70ce612847",
|
||||||
trend: { newlyFound: ["working-tree"], resolved: ["editorconfig"], regressions: [], suppressions: [] },
|
trend: { newlyFound: ["working-tree"], resolved: ["editorconfig"], regressions: [], suppressions: [] },
|
||||||
expiredSuppressions: [],
|
expiredSuppressions: [],
|
||||||
@@ -572,7 +574,12 @@ function createMockDeploymentBridge(context) {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
async gitValidatorSetPolicy(_fullName, policy) {
|
async gitValidatorSetPolicy(_fullName, policy) {
|
||||||
state.gitValidatorPolicy = { id: policy.id, label: policy.id[0].toUpperCase() + policy.id.slice(1) };
|
const requiredScores = { minimal: 55, standard: 70, strict: 82, production: 90 };
|
||||||
|
state.gitValidatorPolicy = {
|
||||||
|
id: policy.id,
|
||||||
|
label: policy.id[0].toUpperCase() + policy.id.slice(1),
|
||||||
|
requiredScore: requiredScores[policy.id] || 70,
|
||||||
|
};
|
||||||
return clone(state.gitValidatorPolicy);
|
return clone(state.gitValidatorPolicy);
|
||||||
},
|
},
|
||||||
async gitValidatorSuppress(_fullName, suppression) {
|
async gitValidatorSuppress(_fullName, suppression) {
|
||||||
|
|||||||
+17
-3
@@ -937,6 +937,21 @@ select:focus-visible {
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.repo-content > .tab-page,
|
||||||
|
.repo-content > .validator-page {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
}
|
||||||
|
.repo-content > .validator-empty,
|
||||||
|
.repo-content > .empty-state {
|
||||||
|
max-height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
.changes-layout {
|
.changes-layout {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -1286,7 +1301,7 @@ html[data-theme="light"] .diff-line.remove {
|
|||||||
padding: 18px;
|
padding: 18px;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
overflow: auto;
|
align-content: start;
|
||||||
}
|
}
|
||||||
.validator-empty {
|
.validator-empty {
|
||||||
min-height: 360px;
|
min-height: 360px;
|
||||||
@@ -2572,9 +2587,8 @@ kbd {
|
|||||||
font: 11px var(--font-mono);
|
font: 11px var(--font-mono);
|
||||||
}
|
}
|
||||||
.tab-page {
|
.tab-page {
|
||||||
min-height: 100%;
|
min-height: 0;
|
||||||
padding: 18px 19px 42px;
|
padding: 18px 19px 42px;
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
.git-tools-grid {
|
.git-tools-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -60,6 +60,24 @@ async function assertSurface(page) {
|
|||||||
expect(audit.headings.length).toBeGreaterThan(0);
|
expect(audit.headings.length).toBeGreaterThan(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function assertScrollableWhenOverflowing(page, selector) {
|
||||||
|
const target = page.locator(selector);
|
||||||
|
await expect(target).toBeVisible();
|
||||||
|
await expect(target).toHaveCSS("overflow-y", /auto|scroll/);
|
||||||
|
let metrics;
|
||||||
|
await expect.poll(async () => {
|
||||||
|
metrics = await target.evaluate((element) => ({
|
||||||
|
connected: element.isConnected,
|
||||||
|
clientHeight: element.clientHeight,
|
||||||
|
scrollHeight: element.scrollHeight,
|
||||||
|
}));
|
||||||
|
return metrics.connected && metrics.clientHeight > 0;
|
||||||
|
}).toBe(true);
|
||||||
|
if (metrics.scrollHeight > metrics.clientHeight + 1) {
|
||||||
|
await target.evaluate((element) => { element.scrollTop = element.scrollHeight; });
|
||||||
|
await expect.poll(() => target.evaluate((element) => element.scrollTop)).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
test("shell, overview, repositories and settings remain responsive and accessible", async ({ page }, testInfo) => {
|
test("shell, overview, repositories and settings remain responsive and accessible", async ({ page }, testInfo) => {
|
||||||
await assertSurface(page);
|
await assertSurface(page);
|
||||||
for (const view of ["overview", "deployments", "settings"]) {
|
for (const view of ["overview", "deployments", "settings"]) {
|
||||||
@@ -86,13 +104,28 @@ test("repository changes, Git tools and Git Validator complete their primary flo
|
|||||||
}
|
}
|
||||||
await page.locator('[data-action="repo-tab"][data-tab="validator"]').click();
|
await page.locator('[data-action="repo-tab"][data-tab="validator"]').click();
|
||||||
await expect(page.locator(".validator-score")).toBeVisible();
|
await expect(page.locator(".validator-score")).toBeVisible();
|
||||||
|
await assertScrollableWhenOverflowing(page, ".validator-page");
|
||||||
await expect(page.locator("#validator-policy")).toBeVisible();
|
await expect(page.locator("#validator-policy")).toBeVisible();
|
||||||
await page.locator("#validator-policy").selectOption("production");
|
await page.locator("#validator-policy").selectOption("production");
|
||||||
await expect(page.locator(".validator-hero")).toContainText(/Production policy/i);
|
await expect(page.locator(".validator-hero")).toContainText(/Production policy/i);
|
||||||
|
await expect(page.locator(".validator-hero")).toContainText(/review required/i);
|
||||||
await page.keyboard.press("Tab");
|
await page.keyboard.press("Tab");
|
||||||
await expect(page.locator(":focus")).toBeVisible();
|
await expect(page.locator(":focus")).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("every long application surface retains a working vertical scroll owner", async ({ page }) => {
|
||||||
|
for (const view of ["overview", "deployments", "diagnostics", "settings"]) {
|
||||||
|
await page.locator(`.nav-button[data-view="${view}"]`).click();
|
||||||
|
await assertScrollableWhenOverflowing(page, ".main-canvas");
|
||||||
|
}
|
||||||
|
await page.locator('[data-action="select-repo"]').first().click();
|
||||||
|
for (const tab of ["history", "deployments", "gittools", "validator", "settings"]) {
|
||||||
|
await page.locator(`[data-action="repo-tab"][data-tab="${tab}"]`).click();
|
||||||
|
const scrollRoot = page.locator(".repo-content > .tab-page, .repo-content > .validator-page");
|
||||||
|
if (await scrollRoot.count())
|
||||||
|
await assertScrollableWhenOverflowing(page, ".repo-content > .tab-page, .repo-content > .validator-page");
|
||||||
|
}
|
||||||
|
});
|
||||||
test("deployment inventory supports dense workloads without ambiguous blank cards", async ({ page }) => {
|
test("deployment inventory supports dense workloads without ambiguous blank cards", async ({ page }) => {
|
||||||
await page.locator('.nav-button[data-action="navigate"][data-view="deployments"]').click();
|
await page.locator('.nav-button[data-action="navigate"][data-view="deployments"]').click();
|
||||||
await expect(page.locator(".deploy-card, .server-inventory-panel .tool-row").first()).toBeVisible();
|
await expect(page.locator(".deploy-card, .server-inventory-panel .tool-row").first()).toBeVisible();
|
||||||
|
|||||||
@@ -23,6 +23,20 @@ test("Git Validator policies enforce score, blockers and enabled checks", () =>
|
|||||||
assert.equal(governed.checks[0].blocking, true);
|
assert.equal(governed.checks[0].blocking, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("built-in policies enforce their declared blocking severities", () => {
|
||||||
|
const finding = [{ id: "readme", status: "warning", category: "Documentation", weight: 5 }];
|
||||||
|
assert.equal(applyPolicy(finding, { id: "minimal" }, []).checks[0].blocking, false);
|
||||||
|
for (const id of ["standard", "strict", "production"])
|
||||||
|
assert.equal(applyPolicy(finding, { id }, []).checks[0].blocking, true, `${id} must block active warnings`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("documented suppressions remove active blockers", () => {
|
||||||
|
const now = new Date("2026-07-01T00:00:00.000Z");
|
||||||
|
const suppression = validateSuppression({ checkId: "readme", reason: "Tracked remediation work", author: "Jens", expiresAt: "2026-07-08T00:00:00.000Z", evidence: "ticket:FF-7" }, normalizePolicy({ id: "standard" }), now);
|
||||||
|
const check = applyPolicy([{ id: "readme", status: "warning", category: "Documentation", weight: 5 }], { id: "standard" }, [suppression], now).checks[0];
|
||||||
|
assert.equal(check.suppressed, true);
|
||||||
|
assert.equal(check.blocking, false);
|
||||||
|
});
|
||||||
test("suppressions require accountable evidence and reactivate after expiry", () => {
|
test("suppressions require accountable evidence and reactivate after expiry", () => {
|
||||||
const now = new Date("2026-07-01T00:00:00.000Z");
|
const now = new Date("2026-07-01T00:00:00.000Z");
|
||||||
const suppression = validateSuppression({ checkId: "signed-tags", reason: "Tracked under release hardening", author: "Jens", ticket: "FF-42", expiresAt: "2026-07-08T00:00:00.000Z", scope: "repository", evidence: "sha:abc" }, normalizePolicy({ id: "standard" }), now);
|
const suppression = validateSuppression({ checkId: "signed-tags", reason: "Tracked under release hardening", author: "Jens", ticket: "FF-42", expiresAt: "2026-07-08T00:00:00.000Z", scope: "repository", evidence: "sha:abc" }, normalizePolicy({ id: "standard" }), now);
|
||||||
|
|||||||
@@ -100,6 +100,24 @@ test("Git Validator recognizes remote aliases and secret-shaped tracked paths",
|
|||||||
assert.equal(isSensitiveTrackedPath(".env.example"), false);
|
assert.equal(isSensitiveTrackedPath(".env.example"), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Git Validator rejects stale or forged repair requests", async () => {
|
||||||
|
const validator = new GitValidatorService({ git: new GitService() });
|
||||||
|
validator.scan = async () => ({
|
||||||
|
checks: [{ id: "local-safety", fixAction: "configure-local-safety", status: "warning" }],
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
(await validator.resolveRepairCheck({}, { id: "local-safety", fixAction: "configure-local-safety" })).id,
|
||||||
|
"local-safety",
|
||||||
|
);
|
||||||
|
await assert.rejects(
|
||||||
|
validator.resolveRepairCheck({}, { id: "local-safety", fixAction: "align-origin" }),
|
||||||
|
/stale/i,
|
||||||
|
);
|
||||||
|
await assert.rejects(
|
||||||
|
validator.resolveRepairCheck({}, { id: "resolved-check", fixAction: "align-origin" }),
|
||||||
|
/resolved|no longer repairable/i,
|
||||||
|
);
|
||||||
|
});
|
||||||
test("Git Validator reports reproducibility, CI and editor hygiene and creates reviewable defaults", async (t) => {
|
test("Git Validator reports reproducibility, CI and editor hygiene and creates reviewable defaults", async (t) => {
|
||||||
const root = await mkdtemp(path.join(os.tmpdir(), "forgeflow-hygiene-"));
|
const root = await mkdtemp(path.join(os.tmpdir(), "forgeflow-hygiene-"));
|
||||||
t.after(() => rm(root, { recursive: true, force: true }));
|
t.after(() => rm(root, { recursive: true, force: true }));
|
||||||
|
|||||||
Reference in New Issue
Block a user