@ai-ecoverse/slicc-browsers 1.10.1 → 1.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/fixture-server.mjs +13 -13
- package/guard-driver.mjs +12 -0
- package/guard-runner.mjs +28 -20
- package/guard-vectors.json +1 -1
- package/package.json +3 -3
- package/protected.mjs +1 -7
- package/unparseable-url.mjs +13 -13
package/README.md
CHANGED
|
@@ -283,6 +283,8 @@ It exports:
|
|
|
283
283
|
- `instrument(client, sessionId, log, tag)`, `load(client, sessionId, url)` and `popup(client, openerId)`;
|
|
284
284
|
- `judge(vector, side, observed)`: `{ pass, refused, skipped, mismatches }`, where `refused` lists the leg's refusals and `skipped` the observations that depend on them or explicitly record unavailable observation capabilities;
|
|
285
285
|
- `result(vector, run)`: a report row from `{ target, variant, transport, via?, capabilities?, control, guarded, live }`. `capabilities` is the transport's; without it, the transport has them all;
|
|
286
|
+
- `parseURL(value, base?)`: a `URL`, or `null` for anything `new URL` rejects or a non-string; every parse of a fixture log entry or a Chrome-reported URL in the runner, the fixture and `protected.mjs` goes through it, so an unparseable entry never throws;
|
|
287
|
+
- `owned(entries, tag)`: the fixture log entries whose raw URL contains `tag`, the row's attribution through its `n=`, `tag=` or `to=` query;
|
|
286
288
|
- `summary({ guard, sha, set, results })`: `set` is the parsed `guard-vectors.json`. It returns `{ set, schemaVersion, guard, sha, variants, targets, transports, results, skipped, notRun, missing, pass, line }`. `missing` lists each `{ id, target, variant, transport }` without a row, for every vector whose `scope` is `any` or the guard, every target it lists, every variant in `variants` or `results`, and every transport in `results`; `pass` needs it empty, where `line` is `GUARD-PASS <guard>@<sha> (vectors @ai-ecoverse/slicc-browsers@X.Y.Z)` or `GUARD-FAIL …`.
|
|
287
289
|
|
|
288
290
|
**`npm run validate`** checks that:
|
|
@@ -437,6 +439,14 @@ redirect chains it also requires the exact final destination request in the
|
|
|
437
439
|
fixture log. A setup beacon on the same origin and tag cannot settle an unfinished
|
|
438
440
|
redirect chain. This does not change the observations used to judge either leg.
|
|
439
441
|
|
|
442
|
+
The fixture log is shared by every row in a run, and `C8.unparseable` leaves entries such
|
|
443
|
+
as `http://exa%20mple.sliccy.ai:<port>/doc?n=<tag>-create-0` that `new URL` rejects. A
|
|
444
|
+
row reads only the entries that carry its own tag; a tagged `liveCheck`, `served`,
|
|
445
|
+
`on` or `beacon` check never sees another row's entries, and an entry that does not
|
|
446
|
+
parse is matched only by its raw or canonical string, never parsed into a throw.
|
|
447
|
+
`hops` checks compare exact URLs. `"tagged": false` reads every entry and skips the ones
|
|
448
|
+
that don't parse.
|
|
449
|
+
|
|
440
450
|
The separately exported Node module `@ai-ecoverse/slicc-browsers/fixture` exposes
|
|
441
451
|
`fixtureServer({ host, port, tls, latency })`, defaulting to a random loopback port. `latency(url)` returns milliseconds to hold that response, for timing tests.
|
|
442
452
|
Pass `tls: { key, cert }` to serve HTTPS using Node TLS key and certificate
|
package/fixture-server.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
2
|
import { createServer as createSecureServer } from 'node:https';
|
|
3
|
+
import { parseURL } from './guard-driver.mjs';
|
|
3
4
|
|
|
4
5
|
const tall = Buffer.from(
|
|
5
6
|
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
|
@@ -78,19 +79,18 @@ function route(req, url, send, html) {
|
|
|
78
79
|
|
|
79
80
|
function requestURL(req, scheme) {
|
|
80
81
|
const origin = `${scheme}://${req.headers.host}`;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
82
|
+
const parsed = parseURL(req.url, origin);
|
|
83
|
+
if (parsed) return parsed;
|
|
84
|
+
const path =
|
|
85
|
+
parseURL(req.url, `${scheme}://fixture.invalid`) ?? new URL(`${scheme}://fixture.invalid/`);
|
|
86
|
+
return {
|
|
87
|
+
pathname: path.pathname,
|
|
88
|
+
searchParams: path.searchParams,
|
|
89
|
+
href: `${origin}${path.pathname}${path.search}${path.hash}`,
|
|
90
|
+
origin,
|
|
91
|
+
host: req.headers.host,
|
|
92
|
+
hostname: String(req.headers.host ?? '').replace(/:\d+$/, ''),
|
|
93
|
+
};
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency } = {}) {
|
package/guard-driver.mjs
CHANGED
|
@@ -275,3 +275,15 @@ export function summary({ guard, sha, set, results }) {
|
|
|
275
275
|
line: `${pass ? 'GUARD-PASS' : 'GUARD-FAIL'} ${guard}@${sha} (vectors ${name})`,
|
|
276
276
|
};
|
|
277
277
|
}
|
|
278
|
+
|
|
279
|
+
export function parseURL(value, base) {
|
|
280
|
+
if (typeof value !== 'string') return null;
|
|
281
|
+
try {
|
|
282
|
+
return new URL(value, base);
|
|
283
|
+
} catch {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export const owned = (entries, tag) =>
|
|
289
|
+
(entries ?? []).filter((entry) => typeof entry?.url === 'string' && entry.url.includes(tag));
|
package/guard-runner.mjs
CHANGED
|
@@ -7,11 +7,13 @@ import {
|
|
|
7
7
|
isRefusal,
|
|
8
8
|
loadedAfter,
|
|
9
9
|
outcome,
|
|
10
|
+
owned,
|
|
11
|
+
parseURL,
|
|
10
12
|
summary as report,
|
|
11
13
|
result,
|
|
12
14
|
steps,
|
|
13
15
|
} from './guard-driver.mjs';
|
|
14
|
-
import { unparseableURLs } from './unparseable-url.mjs';
|
|
16
|
+
import { canonicalURL, unparseableURLs } from './unparseable-url.mjs';
|
|
15
17
|
|
|
16
18
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
19
|
const namedResultMs = 10000;
|
|
@@ -233,13 +235,13 @@ class Execution {
|
|
|
233
235
|
}
|
|
234
236
|
|
|
235
237
|
async attemptCheck(c) {
|
|
236
|
-
const logs = (await this.options.fixture.log()).slice(this.requestStart);
|
|
238
|
+
const logs = owned((await this.options.fixture.log()).slice(this.requestStart), this.vars.tag);
|
|
237
239
|
if (c.pre) {
|
|
238
240
|
const url = address(c.pre);
|
|
239
241
|
return logs.some((entry) => {
|
|
240
|
-
const request =
|
|
242
|
+
const request = parseURL(entry.url);
|
|
241
243
|
return (
|
|
242
|
-
request
|
|
244
|
+
request?.pathname === '/live' &&
|
|
243
245
|
request.searchParams.get('tag') === this.vars.tag &&
|
|
244
246
|
request.searchParams.get('step') === 'pre' &&
|
|
245
247
|
request.searchParams.get('to') === url
|
|
@@ -247,15 +249,16 @@ class Execution {
|
|
|
247
249
|
});
|
|
248
250
|
}
|
|
249
251
|
const url = address(c.loaded);
|
|
252
|
+
const origin = parseURL(url)?.origin;
|
|
250
253
|
return logs.some((entry) => {
|
|
251
|
-
|
|
254
|
+
if (entry.url === url || canonicalURL(entry.url) === canonicalURL(url)) return true;
|
|
255
|
+
const request = parseURL(entry.url);
|
|
252
256
|
return (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
request.searchParams.get('step') !== 'pre')
|
|
257
|
+
request?.pathname === '/live' &&
|
|
258
|
+
request.origin === origin &&
|
|
259
|
+
request.searchParams.get('tag') === this.vars.tag &&
|
|
260
|
+
request.searchParams.get('to') === url &&
|
|
261
|
+
request.searchParams.get('step') !== 'pre'
|
|
259
262
|
);
|
|
260
263
|
});
|
|
261
264
|
}
|
|
@@ -1035,22 +1038,27 @@ class Execution {
|
|
|
1035
1038
|
async requestCheck(c, start = this.requestStart) {
|
|
1036
1039
|
const logs = (await this.options.fixture.log()).slice(start);
|
|
1037
1040
|
if (c.hops)
|
|
1038
|
-
return c.hops.every((_, i) =>
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
+
return c.hops.every((_, i) =>
|
|
1042
|
+
logs.some((entry) => entry?.url === hop(c.hops.slice(i), c.to))
|
|
1043
|
+
);
|
|
1044
|
+
const mine =
|
|
1045
|
+
c.tagged === false
|
|
1046
|
+
? logs.filter((entry) => typeof entry?.url === 'string')
|
|
1047
|
+
: owned(logs, this.vars.tag);
|
|
1048
|
+
return mine.some((entry) => {
|
|
1049
|
+
const url = parseURL(entry.url);
|
|
1041
1050
|
if (c.beacon)
|
|
1042
1051
|
return (
|
|
1043
|
-
url
|
|
1052
|
+
url?.pathname === '/beacon' &&
|
|
1044
1053
|
url.searchParams.get('tag') === this.vars.tag &&
|
|
1045
1054
|
url.searchParams.get('origin') === c.beacon
|
|
1046
1055
|
);
|
|
1047
|
-
if (c.
|
|
1048
|
-
if (c.origin && url.origin !== c.origin) return false;
|
|
1056
|
+
if (c.origin && url?.origin !== c.origin) return false;
|
|
1049
1057
|
if (c.served)
|
|
1050
|
-
return c.served.startsWith('http') ? entry.url === c.served : url
|
|
1058
|
+
return c.served.startsWith('http') ? entry.url === c.served : url?.pathname === c.served;
|
|
1051
1059
|
return (
|
|
1052
|
-
(c.on.startsWith('http') ? entry.url === c.on : url
|
|
1053
|
-
entry.headers[c.header.toLowerCase()] === (c.value ?? this.vars.tag)
|
|
1060
|
+
(c.on.startsWith('http') ? entry.url === c.on : url?.pathname === c.on) &&
|
|
1061
|
+
entry.headers?.[c.header.toLowerCase()] === (c.value ?? this.vars.tag)
|
|
1054
1062
|
);
|
|
1055
1063
|
});
|
|
1056
1064
|
}
|
package/guard-vectors.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-browsers",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.2",
|
|
4
4
|
"description": "SLICC's shared browser catalog, JSON schemas and discovery conformance corpus",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -67,14 +67,14 @@
|
|
|
67
67
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish",
|
|
68
68
|
"release": "semantic-release",
|
|
69
69
|
"test:guard": "npm run test:guard-integration && npm run test:guard-sweep",
|
|
70
|
-
"test:guard-integration": "node --test --test-concurrency=2 test/integration/guard-lifecycle.test.mjs test/integration/guard-fit.test.mjs test/integration/guard-chain.test.mjs test/integration/guard-validation.test.mjs test/integration/guard-liveness.test.mjs test/integration/guard-observations.test.mjs test/integration/guard-child-latency.test.mjs test/integration/guard-adoption.test.mjs test/integration/guard-scoping.test.mjs test/integration/guard-late-pause.test.mjs test/integration/guard-legs.test.mjs test/integration/guard-fetch-enable.test.mjs test/integration/guard-diagnostics.test.mjs test/integration/guard-chrome.test.mjs test/integration/guard-worlds.test.mjs test/integration/guard-world-discovery.test.mjs test/integration/guard-world-guesses.test.mjs test/integration/guard-unparseable.test.mjs test/integration/guard-scales.test.mjs",
|
|
70
|
+
"test:guard-integration": "node --test --test-concurrency=2 test/integration/guard-lifecycle.test.mjs test/integration/guard-fit.test.mjs test/integration/guard-chain.test.mjs test/integration/guard-validation.test.mjs test/integration/guard-liveness.test.mjs test/integration/guard-observations.test.mjs test/integration/guard-child-latency.test.mjs test/integration/guard-adoption.test.mjs test/integration/guard-scoping.test.mjs test/integration/guard-late-pause.test.mjs test/integration/guard-legs.test.mjs test/integration/guard-fetch-enable.test.mjs test/integration/guard-diagnostics.test.mjs test/integration/guard-chrome.test.mjs test/integration/guard-worlds.test.mjs test/integration/guard-world-discovery.test.mjs test/integration/guard-world-guesses.test.mjs test/integration/guard-unparseable.test.mjs test/integration/guard-scales.test.mjs test/integration/guard-shared-log.test.mjs",
|
|
71
71
|
"test:guard-sweep": "node scripts/test-guards.mjs",
|
|
72
72
|
"test:gates": "node --test test/integration/gates.test.mjs",
|
|
73
73
|
"test:guard-target": "node --test test/integration/guard-runner.test.mjs",
|
|
74
74
|
"test:unit:guard": "node --experimental-test-coverage --test --test-coverage-include=guard-runner.mjs --test-coverage-include=fixture-server.mjs --test-coverage-lines=100 test/unit/guard-*.test.mjs"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@ai-ecoverse/slicc-shared-web": "1.
|
|
77
|
+
"@ai-ecoverse/slicc-shared-web": "1.27.0",
|
|
78
78
|
"@biomejs/biome": "2.5.15",
|
|
79
79
|
"ajv": "8.20.0",
|
|
80
80
|
"ajv-formats": "3.0.1",
|
package/protected.mjs
CHANGED
package/unparseable-url.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { outcome } from './guard-driver.mjs';
|
|
1
|
+
import { outcome, owned, parseURL } from './guard-driver.mjs';
|
|
2
2
|
|
|
3
3
|
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4
4
|
const refused = (out) => !out.ok && /^slicc: refused:/.test(out.error?.message ?? '');
|
|
@@ -14,17 +14,17 @@ export const rootURL = (root, host, path) =>
|
|
|
14
14
|
`${root.protocol}//${host}${root.port ? `:${root.port}` : ''}${path}`;
|
|
15
15
|
|
|
16
16
|
export function canonicalURL(value) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
17
|
+
if (typeof value !== 'string') return value;
|
|
18
|
+
const direct = parseURL(value);
|
|
19
|
+
if (direct) return direct.href;
|
|
20
|
+
const match = /^(https?:)\/\/([^/?#]+)(.*)$/.exec(value);
|
|
21
|
+
if (!match) return value;
|
|
22
|
+
const [, protocol, authority, path] = match;
|
|
23
|
+
const host = authority.replace(/:\d*$/, '').replaceAll(' ', '%20');
|
|
24
|
+
const port = /:(\d*)$/.exec(authority)?.[1];
|
|
25
|
+
const parsed = parseURL(`${protocol}//example.test${port ? `:${port}` : ''}${path}`);
|
|
26
|
+
if (!parsed) return value;
|
|
27
|
+
return rootURL(parsed, host, `${parsed.pathname}${parsed.search}${parsed.hash}`);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
async function document(reader, targetId, expected, allowNativeRejection = false) {
|
|
@@ -212,7 +212,7 @@ export async function unparseableURLs({
|
|
|
212
212
|
};
|
|
213
213
|
await pause(100);
|
|
214
214
|
const requests = await fixture.log();
|
|
215
|
-
result.cookieRequest = requests.some((entry) =>
|
|
215
|
+
result.cookieRequest = owned(requests, tag).some((entry) =>
|
|
216
216
|
entry.url.includes(`${tag}-create-`) || entry.url.includes(`${tag}-navigate-`)
|
|
217
217
|
? entry.headers.cookie?.includes(`${cookie}=${value}`)
|
|
218
218
|
: false
|