@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 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
@@ -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
- try {
82
- return new URL(req.url, origin);
83
- } catch {
84
- const path = new URL(req.url, `${scheme}://fixture.invalid`);
85
- return {
86
- pathname: path.pathname,
87
- searchParams: path.searchParams,
88
- href: `${origin}${path.pathname}${path.search}${path.hash}`,
89
- origin,
90
- host: req.headers.host,
91
- hostname: req.headers.host.replace(/:\d+$/, ''),
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 = new URL(entry.url);
242
+ const request = parseURL(entry.url);
241
243
  return (
242
- request.pathname === '/live' &&
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
- const request = new URL(entry.url);
254
+ if (entry.url === url || canonicalURL(entry.url) === canonicalURL(url)) return true;
255
+ const request = parseURL(entry.url);
252
256
  return (
253
- entry.url === url ||
254
- (request.pathname === '/live' &&
255
- request.origin === new URL(url).origin &&
256
- request.searchParams.get('tag') === this.vars.tag &&
257
- request.searchParams.get('to') === url &&
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) => logs.some((entry) => entry.url === hop(c.hops.slice(i), c.to)));
1039
- return logs.some((entry) => {
1040
- const url = new URL(entry.url);
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.pathname === '/beacon' &&
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.tagged !== false && !entry.url.includes(this.vars.tag)) return false;
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.pathname === c.served;
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.pathname === c.on) &&
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
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "setVersion": "1.10.1",
3
+ "setVersion": "1.10.2",
4
4
  "sources": [
5
5
  {
6
6
  "repo": "ai-ecoverse/slicc-cli",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-browsers",
3
- "version": "1.10.1",
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.25.2",
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
@@ -1,10 +1,4 @@
1
- const parse = (text) => {
2
- try {
3
- return new URL(text);
4
- } catch {
5
- return null;
6
- }
7
- };
1
+ import { parseURL as parse } from './guard-driver.mjs';
8
2
 
9
3
  const scheme = (u) => u.protocol.slice(0, -1);
10
4
 
@@ -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
- try {
18
- return new URL(value).href;
19
- } catch {
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 = new URL(`${protocol}//example.test${port ? `:${port}` : ''}${path}`);
26
- return rootURL(parsed, host, `${parsed.pathname}${parsed.search}${parsed.hash}`);
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