@jrpool/kilotest 39.0.1 → 40.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,13 +12,13 @@ const {sendAlert} = require('../alerts');
12
12
 
13
13
  // Returns the response body.
14
14
  exports.response = async args => {
15
- const [request = ''] = args;
15
+ const [feature = ''] = args;
16
16
  // Initialize the response content.
17
17
  const responseContent = {
18
18
  'details about your request': null
19
19
  };
20
- // If the request is empty:
21
- if (!request) {
20
+ // If the requested feature or improvement is empty:
21
+ if (!feature) {
22
22
  // Add this to the response content.
23
23
  responseContent['details about your request'] = {
24
24
  error: 'request invalid: request is empty'
@@ -27,7 +27,7 @@ exports.response = async args => {
27
27
  // Otherwise, i.e. if it exists:
28
28
  else {
29
29
  // Notify the manager.
30
- await sendAlert('MCP feature request received', request);
30
+ await sendAlert('MCP feature request received', feature);
31
31
  // Add the disposition to the response content.
32
32
  responseContent['details about your request'] = {
33
33
  'date and time received': new Date().toISOString(),
@@ -43,7 +43,7 @@ exports.response = async args => {
43
43
  method: 'POST',
44
44
  URL: `${thisHost}/api/requestFeature`,
45
45
  body: {
46
- request
46
+ feature
47
47
  },
48
48
  'closest ancestor request': null
49
49
  },
package/api/routes.js CHANGED
@@ -90,7 +90,7 @@ module.exports = [
90
90
  operationId: 'requestFeature',
91
91
  method: 'post',
92
92
  path: '/api/requestFeature',
93
- summary: 'Request a new feature or feature improvement',
93
+ summary: 'Request a feature improvement or new feature',
94
94
  bodySchema: z.object(requestFeatureSchema),
95
95
  responseSchema: requestFeatureResponseSchema
96
96
  }
package/api/schemas.js CHANGED
@@ -52,7 +52,7 @@ exports.requestRetestSchema = {
52
52
 
53
53
  // requestFeature: POST /api/requestFeature
54
54
  exports.requestFeatureSchema = {
55
- request: z.string().describe('description of requested new feature or feature improvement')
55
+ feature: z.string().describe('description of requested feature improvement or new feature')
56
56
  };
57
57
 
58
58
  // listReports takes no input; omitted (mcp.js already uses inputSchema: {}).
@@ -105,11 +105,16 @@ const thisRequestSchema = (method, bodySchema) => z.object({
105
105
  'closest ancestor request': requestReferenceSchema.nullable()
106
106
  });
107
107
 
108
- const envelope = (method, responseContentSchema, bodySchema) => z.object({
108
+ const envelope = (
109
+ method,
110
+ responseContentSchema,
111
+ bodySchema,
112
+ /** @type {z.ZodType} */ similarWebSchema = similarWebRequestsSchema
113
+ ) => z.object({
109
114
  'tool collection': toolsFactsSchema,
110
115
  'tool name': z.string(),
111
116
  'this request': thisRequestSchema(method, bodySchema),
112
- 'URLs of similar requests for web users': similarWebRequestsSchema,
117
+ 'URLs of similar requests for web users': similarWebSchema,
113
118
  'response metadata': responseMetadataSchema,
114
119
  'response content': responseContentSchema
115
120
  });
@@ -293,5 +298,6 @@ exports.requestFeatureResponseSchema = envelope(
293
298
  disposition: z.string()
294
299
  })
295
300
  ])}),
296
- z.object(exports.requestFeatureSchema)
301
+ z.object(exports.requestFeatureSchema),
302
+ z.null()
297
303
  );
package/api/testSuite.js CHANGED
@@ -272,13 +272,17 @@ const requestService = async () => {
272
272
  return;
273
273
  }
274
274
  console.log('======================\nRequest: Make a feature request');
275
- path = `/api/requestFeature/${timeStamp}/${jobID}`;
275
+ path = '/api/requestFeature';
276
+ const featureRequest = 'Do the impossible';
276
277
  body = await submitRequest(path, method, {
277
- feature: 'Do the impossible'
278
+ feature: featureRequest
278
279
  });
279
- responseContent = body?.['response content'] ?? {};
280
- requestDetails = responseContent['details about your request'] ?? {};
281
- if (!requestDetails.disposition?.includes('received and logged')) {
280
+ const feature = body?.['this request']?.body?.feature ?? {};
281
+ requestDetails = body?.['response content']?.['details about your request'] ?? {};
282
+ if (
283
+ feature !== featureRequest
284
+ || !requestDetails.disposition?.includes('received and logged')
285
+ ) {
282
286
  console.log(`requestDetails: ${JSON.stringify(requestDetails, null, 2)}`);
283
287
  return;
284
288
  }
package/docs/SERVICE.md CHANGED
@@ -280,6 +280,50 @@ Experimentation revealed that a high-frequency instance could decrease the elaps
280
280
 
281
281
  ## Security
282
282
 
283
+ ### Report protection
284
+
285
+ Jobs and reports are not tracked, so there are no duplicates in any other copy of the repository. Jobs are typically ephemeral, but reports typically remain in existence until deemed obsolete and useless even for historical comparison. Therefore, reports risk deletion unless duplicates are made externally. Other files, including application code, dependencies, and `db/reportsExtract.json`, are not at risk, because they can be pushed from the local repository or regenerated.
286
+
287
+ Reports created on the deployed server are currently protected with an external archive at the [Cloudflare R2 object storage service](https://developers.cloudflare.com/r2/). On that service, the current Kilotest maintainer has an account, subscribes to the R2 service, has created two _buckets_ named `kilotest-reports` and `kilotest-hidden-reports`, and has created an API token scoped to those buckets with object read-write permissions, access and secret access keys for S3 clients, and a restriction to the IPv4 and IPv6 addresses of the server.
288
+
289
+ The server host uses `rclone` for file synchronization with external storage locations. Two files on the server host enable `rclone` for use by Kilotest. One file is `/home/linuxuser/.config/rclone/rclone.conf`. Its content, with secrets replaced, is:
290
+
291
+ ```ini
292
+ [r2]
293
+ type = s3
294
+ provider = Cloudflare
295
+ access_key_id = <access_key_id>
296
+ secret_access_key = <secret_access_key>
297
+ endpoint = https://<account_id>.r2.cloudflarestorage.com
298
+ acl = private
299
+ no_head = true
300
+ no_check_bucket = true
301
+ ```
302
+
303
+ The other file is the `crontab` configuration file for `linuxuser`, which is managed with `crontab -e`. The commands in that file (shown with `crontab -l`) are:
304
+
305
+ ```crontab
306
+ 11 9 * * * /usr/bin/rclone copy /opt/jpdev/kilotest/db/hiddenReports r2:kilotest-hidden-reports/ -v --backup-dir r2:kilotest-hidden-reports-old/$(date +\%F) > /home/linuxuser/kilotest-to-r2.log 2>&1
307
+ 12 9 * * * /usr/bin/rclone copy /opt/jpdev/kilotest/db/reports r2:kilotest-reports/ -v --backup-dir r2:kilotest-reports-old/$(date +\%F) >> /home/linuxuser/kilotest-to-r2.log 2>&1
308
+ ```
309
+
310
+ This configuration every morning (UTC) copies reports to R2 as follows:
311
+
312
+ - Reports already on R2 are not copied again.
313
+ - Reports not yet on R2 are copied to it.
314
+ - Reports on R2 that differ from and are older than the same-name reports on the server are moved to the corresponding `-old` directory, and the newer reports are copied to R2.
315
+
316
+ Each daily copy operation is logged in `/home/linuxuser/kilotest-to-r2.log`. That file is replaced with a new one each day documenting only the operation on that day.
317
+
318
+ If restoration becomes necessary, the files can be copied in the opposite direction with:
319
+
320
+ ```bash
321
+ /usr/bin/rclone copy r2:kilotest-reports /opt/jpdev/kilotest/db/restored-reports -v
322
+ /usr/bin/rclone copy r2:kilotest-hidden-reports /opt/jpdev/kilotest/db/restored-hidden-reports -v
323
+ ```
324
+
325
+ After that the maintainer can inspect the original and restored directories and delete or move report files as needed. After that the `db/reportsExtract.json` file should be deleted, so it will be regenerated when next needed.
326
+
283
327
  ### Possible future Testaro integration
284
328
 
285
329
  Kilotest uses Testaro to run jobs. In previous versions of Kilotest, Testaro was a dependency. It is currently not a dependenc. Instead, Testaro instances are installed on one or more other hosts, and each instance polls Kilotest to ask for jobs to run.
@@ -292,7 +336,7 @@ Testaro uses Playwright to launch and control headless browsers, often `chromium
292
336
 
293
337
  When either Playwright or Puppeteer launches a `chromium` browser, in most environments it is [sandboxed](https://www.geeksforgeeks.org/ethical-hacking/what-is-browser-sandboxing/). Sandboxing is a security feature that prevents the browser from accessing potentially unsafe system resources. But in the Ubuntu Linux operating system that was installed on the Vultr Cloud Compute host a sandboxed browser requires an [unprivileged user namespace](https://ubuntu.com/blog/ubuntu-23-10-restricted-unprivileged-user-namespaces), and when Ubuntu was installed its configuration disallowed such namespaces. The file `/etc/sysctl.d/99-kilotest-userns.conf` with the content `kernel.apparmor_restrict_unprivileged_userns = 1` prohibited unprivileged user namespaces and thereby made sandboxed browsers unlaunchable.
294
338
 
295
- ### Potential modification
339
+ #### Potential modification
296
340
 
297
341
  One modification to cope with this prohibition on the Vultr Cloud Compute host would be to configure Playwright and Puppeteer to launch `chromium` non-sandboxed. In both cases, launch arguments `'--no-sandbox'` and `'--disable-setuid-sandbox'` are available to specify this.
298
342
 
@@ -300,7 +344,7 @@ One modification to cope with this prohibition on the Vultr Cloud Compute host w
300
344
  - For the `qualWeb` tool, this would be done in the Testaro `tests/qualweb.js` file, where the `qualWeb.start` method is called with an options argument. Its `args` array property would include `'--no-sandbox'` and `'--disable-setuid-sandbox'`.
301
345
  - The `ibm` tool, too, could launch a Puppeteer `chromium` browser, if page content instead of a Playwright page were passed to the `accessibilityChecker.getCompliance` method, or if the implementation of the tool were changed in the future. For anticipation of such a case, the Testaro `aceconfig.js` file, of which a copy has been created at the root of the Kilotest project, would be modified. That file defines a `module.exports` object with a `puppeteerArgs` property, and, `--no-sandbox` and `--disable-setuid-sandbox` would be added to its array value.
302
346
 
303
- ### Current modification
347
+ #### Current modification
304
348
 
305
349
  However, non-sandboxed browsers are less secure than sandboxed ones, particularly when there is no restriction on who can use the service and what web pages they can test with it. Such restrictions are currently in place, but still permit testing of arbitrary web pages and may be relaxed in the future. Therefore, the potential modification described above would introduce nontrivial risk. An alternative solution was adopted instead. In it, the `chromium` configuration was left unchanged.
306
350
 
package/index.js CHANGED
@@ -750,10 +750,10 @@ const requestHandler = async (request, response) => {
750
750
  }
751
751
  // Otherwise, if the service is to receive a feature request:
752
752
  else if (segments[0] === 'requestFeature') {
753
- const {request} = postData;
753
+ const {feature} = postData;
754
754
  // Get the response body.
755
755
  const responseBody = await require(path.join(__dirname, 'api', 'requestFeature'))
756
- .response(segments.slice(1).concat(request));
756
+ .response([feature]);
757
757
  // Send it.
758
758
  setHeaders('application/json', null, 'ultra');
759
759
  response.end(JSON.stringify(responseBody));
package/openapi.yaml CHANGED
@@ -2,7 +2,7 @@ openapi: 3.1.0
2
2
  info:
3
3
  title: Kilotest API
4
4
  description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 10 rule engines to test public web pages for front-end quality (accessibility, usability, and standards conformity). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
5
- version: 39.0.0
5
+ version: 39.0.3
6
6
  license:
7
7
  name: MIT
8
8
  identifier: MIT
@@ -1045,21 +1045,21 @@ paths:
1045
1045
  /api/requestFeature:
1046
1046
  post:
1047
1047
  operationId: requestFeature
1048
- summary: Request a new feature or feature improvement
1048
+ summary: Request a feature improvement or new feature
1049
1049
  requestBody:
1050
1050
  content:
1051
1051
  application/json:
1052
1052
  schema:
1053
1053
  type: object
1054
1054
  properties:
1055
- request:
1055
+ feature:
1056
1056
  type: string
1057
- description: description of requested new feature or feature improvement
1057
+ description: description of requested feature improvement or new feature
1058
1058
  required:
1059
- - request
1059
+ - feature
1060
1060
  responses:
1061
1061
  '200':
1062
- description: Request a new feature or feature improvement
1062
+ description: Request a feature improvement or new feature
1063
1063
  content:
1064
1064
  application/json:
1065
1065
  schema:
@@ -1082,11 +1082,11 @@ paths:
1082
1082
  body:
1083
1083
  type: object
1084
1084
  properties:
1085
- request:
1085
+ feature:
1086
1086
  type: string
1087
- description: description of requested new feature or feature improvement
1087
+ description: description of requested feature improvement or new feature
1088
1088
  required:
1089
- - request
1089
+ - feature
1090
1090
  additionalProperties: false
1091
1091
  closest ancestor request:
1092
1092
  anyOf:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrpool/kilotest",
3
- "version": "39.0.1",
3
+ "version": "40.0.0",
4
4
  "description": "Ensemble testing for front-end web quality",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -20,6 +20,7 @@ const outputPath = path.join(__dirname, '..', 'openapi.yaml');
20
20
 
21
21
  // Builds the paths object for createDocument from the route table.
22
22
  const buildPaths = () => {
23
+ /** @type {import('zod-openapi').ZodOpenApiPathsObject} */
23
24
  const paths = {};
24
25
  routes.forEach(route => {
25
26
  const {method, path: routePath, summary, pathParamsSchema, bodySchema, responseSchema} = route;
package/CLAUDE.md DELETED
@@ -1,5 +0,0 @@
1
- # Cascade Rules
2
-
3
- - Ask mode: If the current mode is Ask, answer questions and provide code, diffs, or edit suggestions in code blocks or as plain text. Do not invite the user to switch to Normal or Code mode or any mode that permits writing files.
4
- - Comply with the current convention to omit line breaks in long lines in `.md` files and in code comments.
5
- - In conversation responses (not files), when a Markdown table cell would contain a line longer than 120 characters divided by the number of columns, insert `<br>` tags to keep displayed line lengths shorter than that maximum, since raw newlines are not valid inside table cells. If this rule makes any table cumbersome, use a non-tabular format, such as a list, instead.