@ai-ecoverse/slicc-browsers 1.13.4 → 1.13.6

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
@@ -190,7 +190,7 @@ The `C9` foreign extension isolated-world family requires `extensionWorld`: a br
190
190
 
191
191
  The integration launcher loads shared-web 1.25.2’s harness extension exactly once through `Extensions.loadUnpacked` (`ankmbodmgimjemnojmeddfcdlbocolob`), whose content script runs on HTTP(S) pages. shared-web 1.25.2 also loads it by default in its own harness. A transport unable to host a foreign content-script extension omits `extensionWorld` and reports `not-supported:extensionWorld`; an advertised capability with no observed world fails, rather than silently skipping.
192
192
 
193
- `C2.redirect-secret` probes ordinary fixture 302s to seven’s `/auth/callback?code=…&state=…` and vault’s `/oauth/mcp-callback?code=…`, plus `#access_token=…` variants of both. It navigates with Page and scripted `location.href`, fetches a subresource and sends an XHR. The 302 carries `Location`, `Content-Location`, `Refresh: 0;url=…` and `Link: <…>; rel=preconnect`. Page, Network (including ExtraInfo), Runtime, Log, Audits and both Fetch stages are enabled. Every received raw message is scanned, including replies but not the probe’s own observer session traffic, across an eight-second tail, for raw, percent-encoded, JSON-escaped and base64 marker representations. Both response-body APIs are attempted on each hop; guarded bodies need matched refusals. `Network.getResponseBody` is attempted, once per id, with every network id the client learns for a 301–308 hop: the `requestId` of a Network event carrying the redirect status and the `networkId` of the Fetch response-stage pause. For each registered hop whose id the client learned neither way, the probe reads the hop’s real id from the harness’s own unguarded tap (the observer session, which enables Network on the guarded leg) and attempts the read on the guarded session with it. The evidence’s `networkBodyCarriers` counts the ids per carrier, and `networkBodyPath` names the weakest carrier any id needed: `withheld+tap`, then `fetch-networkId`, then `network`, or `none` without any id. These reads never hold up a Fetch pause, and the probe gives them five seconds after `Fetch.disable` to settle. A read still unanswered then is not a refusal. `networkBodyAttempted` and `networkBodyRefused` hold only when every attempt is refused. On the guarded leg a `fetch-networkId` or `withheld+tap` carrier also needs the same row’s unguarded control to have seen the redirect’s Network events. Without that proof, or with no id at all, both read `incomplete` and the probe returns `redirect-secret: network body leg <reason>`; an attempt that is not refused is measured and fails. Every `Network.requestWillBeSent` the client sees for a registered redirect URL must end within the step in a redirect `requestWillBeSent`, `loadingFinished` or `loadingFailed` for that `requestId`. The evidence’s `hops` records per `requestId` the `delivery` (`redacted` for an origin-only protected redirect in place, `withheld` for a terminal without the redirect, `ordinary`, `raw` or `unterminated`) and the `terminal` event method. `requestsTerminated` is false on any `unterminated` request, a measured guard failure rather than missing evidence; `hopsRedacted` is false on any `raw` or `ordinary` hop, so a delivered protected hop must be origin-only, never the raw or a substituted URL; the guarded leg expects it. Page frame trees, resource trees and navigation history are read before recovery to catch error-page leftovers. A separate structural check requires every protected `Network.Request.urlFragment` and `Page.Frame.urlFragment` to be empty or absent, even if its value contains no marker; the ordinary `#access_token` control independently requires fragment evidence in request events, frame events, frame-tree replies and resource-tree replies. The ordinary session must survive. The unguarded ordinary redirect control preserves all four headers in `redirectResponse`, ExtraInfo’s headers and `headersText`, and Fetch’s response headers. `C2.redirect-secret-mix` repeats that matrix with 301/303/307/308. The protected leg requires in-place ExtraInfo delivery with origin-only header values, not merely the absence of a marker.
193
+ `C2.redirect-secret` probes ordinary fixture 302s to seven’s `/auth/callback?code=…&state=…` and vault’s `/oauth/mcp-callback?code=…`, plus `#access_token=…` variants of both. It navigates with Page and scripted `location.href`, fetches a subresource and sends an XHR. The 302 carries `Location`, `Content-Location`, `Refresh: 0;url=…` and `Link: <…>; rel=preconnect`. Page, Network (including ExtraInfo), Runtime, Log, Audits and both Fetch stages are enabled. Every received raw message is scanned, including replies but not the probe’s own observer session traffic, across an eight-second tail, for raw, percent-encoded, JSON-escaped and base64 marker representations. Both response-body APIs are attempted on each hop; guarded bodies need matched refusals. `Network.getResponseBody` is attempted, once per id, with every network id the client learns for a 301–308 hop: the `requestId` of a Network event carrying the redirect status and the `networkId` of the Fetch response-stage pause. For each registered hop whose id the client learned neither way, the probe reads the hop’s real id from the harness’s own unguarded tap (the observer session, which enables Network on the guarded leg) and attempts the read on the guarded session with it. The evidence’s `networkBodyCarriers` counts the ids per carrier, and `networkBodyPath` names the weakest carrier any id needed: `withheld+tap`, then `fetch-networkId`, then `network`, or `none` without any id. These reads never hold up a Fetch pause, and the probe gives them five seconds after `Fetch.disable` to settle. A read still unanswered then is not a refusal. `networkBodyAttempted` and `networkBodyRefused` hold only when every attempt is refused. On the guarded leg a `fetch-networkId` or `withheld+tap` carrier also needs the same row’s unguarded control to have seen the redirect’s Network events. Without that proof, or with no id at all, both read `incomplete` and the probe returns `redirect-secret: network body leg <reason>`; an attempt that is not refused is measured and fails. Every request the client sees for a registered redirect URL, through its initial `Network.requestWillBeSent` or a redirect `requestWillBeSent` whose `redirectResponse.url` is that URL, must end within the step in a redirect `requestWillBeSent`, `loadingFinished` or `loadingFailed` for that `requestId`. The evidence’s `hops` records per `requestId` the `delivery` (`redacted` for an origin-only protected redirect in place, `withheld` for a terminal without the redirect, `ordinary`, `raw` or `unterminated`) and the `terminal` event method. `requestsTerminated` is false on any `unterminated` request, a measured guard failure rather than missing evidence; `hopsRedacted` is false on any `raw` or `ordinary` hop, so a delivered protected hop must be origin-only, never the raw or a substituted URL; the guarded leg expects it. Page frame trees, resource trees and navigation history are read before recovery to catch error-page leftovers. A separate structural check requires every protected `Network.Request.urlFragment` and `Page.Frame.urlFragment` to be empty or absent, even if its value contains no marker; the ordinary `#access_token` control independently requires fragment evidence in request events, frame events, frame-tree replies and resource-tree replies. The ordinary session must survive. The unguarded ordinary redirect control preserves all four headers in `redirectResponse`, ExtraInfo’s headers and `headersText`, and Fetch’s response headers. `C2.redirect-secret-mix` repeats that matrix with 301/303/307/308. The protected leg requires in-place ExtraInfo delivery with origin-only header values, not merely the absence of a marker.
194
194
 
195
195
  `C2.redirect-body` calls `Network.loadNetworkResource` on an ordinary redirect with `includeCredentials: true`, reads any stream with `IO.read`, replays a redirected XHR, requests Page resource content, and captures Page/MHTML and DOM snapshots after a main-frame redirect. It also captures an ordinary parent containing an iframe redirected to vault, including its error document, and overrides a paused ordinary Fetch request to the allowed redirect URL. No protected marker may appear in any message or body, and no request from those operations may send the independently seeded seven cookie. The ordinary control must return the body, resource content and snapshots, and accept the URL override. Its network-body carrier, `networkBodyAttempted`, `networkBodyRefused`, `hops`, `requestsTerminated` and `hopsRedacted` follow `C2.redirect-secret`. Each probe cleans up its redirect registrations, observer session and cookie. The focused Chrome tests use HTTPS with SPKI-pinned fixture trust and default-port protected URLs; host resolver rules route seven and vault to the fixture rather than the public service.
196
196
 
@@ -224,7 +224,7 @@ A client may register `Fetch.enable` patterns naming a protected origin in eithe
224
224
  - `observe?`: each observed name, as `{ "means", "dependsOn"?, "requires"?, "eitherWhen"? }`. `requires` gates only that observation when it is not also a whole-vector requirement: unavailable evidence is recorded as `"not-supported"`, excluded from that field's expectations, and reported in the row's `notSupported` list. `dependsOn` lists the refusals the observation depends on (see Refusals). `eitherWhen` is a check: when it holds on the guarded leg, that observation's expectation becomes `"either"` there (see Design-dependent outcomes);
225
225
  - `live`: the liveness check;
226
226
  - `control` (`origin: "ordinary"`, `via`, `expected`) and `expected`: an object of names and booleans, or text;
227
- - `skip?` (`target`, `reason`): `reason` starts with a code, as in `not-applicable: …`.
227
+ - `skip?` (`target`, `reason`): `reason` starts with a code, as in `not-applicable: …`. A runner reports the row on `target` as `skip` with this `reason`, unless a capability is missing.
228
228
 
229
229
  **Steps** are declarative and transport-neutral. Any step can take `on` (`control` or `guarded`), which runs it on that leg only.
230
230
  Any `cdp`, `page` or `children` step can take `requires`, the capabilities it needs: `autoAttach` for `Target.setAutoAttach`, `children` and `session: "child"`, `browserSession` for `session: "browser"`, `browserContexts` for `Target.createBrowserContext` and `Target.disposeBrowserContext`, and `chromeUrlsAttach` for a step that must attach to a `chrome://` page to pass.
@@ -279,7 +279,7 @@ For remote/page harnesses, `fixtureClient(base)` is exported by `./guard-runner`
279
279
 
280
280
  **Design-dependent outcomes.** A key listed in `designDependent` may expect `"either"`, which `true` and `false` both match: a guard that refuses a setup step lets the real document load, and one that blocks before commit doesn't. `detached` is design-dependent on the rows whose guarded leg proves the block (the pre-action beacon plus an exact-URL block or the history form): its `eitherWhen` holds the same proof, so a guard that blocks without detaching passes, and one that neither blocks nor detaches still fails. The hard negatives stay required.
281
281
 
282
- **Observability.** `observable.own: false` marks a vector the guard host's own origin can't show (headers, auth, redirects); a runner whose `own` is a static server reports it as `skip` with `not-observable`.
282
+ **Observability.** `observable.own: false` marks a vector the guard host's own origin can't show (headers, auth, redirects); a runner reports it as `skip` with `not-applicable` on that target.
283
283
 
284
284
  **Applies to.** `appliesTo` lists the triggers a vector runs with (`href`, `iframe`, `popup`, `redirect`). Chrome doesn't apply some session overrides inside a cross-site iframe, so those E6 rows list `href` only.
285
285
 
@@ -306,9 +306,11 @@ It exports:
306
306
  - `waitFor(check, ms, step?)`: polls `check` every `step` ms (default 25) until it holds, checking once more after the bound. A timer heartbeat every `step` runs for the whole wait, including while `check` is awaiting a reply, and any gap between beats longer than a second (or four steps, if longer) extends the bound by that gap, so a synchronous event-loop stall elsewhere in the process does not consume it. The extensions add up to at most `max(4 × ms, 30000)` ms, so a throttled page cannot stretch a wait without limit. `load`, the runner's waits and the error-page and redirect-body probes use it;
307
307
  - `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;
308
308
  - `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;
309
+ - `skipReason(vector, target, scope)`: the reason the runner skips the row with when it has every capability: the vector's `skip.reason` when its `skip.target` is `target`, `not-applicable` when its `observable[target]` is `false` or its `scope` is neither `any` nor `scope`, otherwise `null`; `inapplicable(vector, target, scope)` is whether it isn't `null`;
310
+ - `knownOrigins` and `originDefaults`: the origin names a runner can miss, and the origins `plain`, `cross`, `log` and `own` fall back to when unset;
309
311
  - `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 and the fixture goes through it (`protected.mjs` keeps its own copy, since it imports nothing), so an unparseable entry never throws;
310
312
  - `owned(entries, tag)`: the fixture log entries whose raw URL contains `tag`, the row's attribution through its `n=`, `tag=` or `to=` query;
311
- - `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 …`.
313
+ - `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, forged, 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 …`. `forged` lists each `{ id, target, variant, transport, why }` that is `not-run` on a vector that isn't `kind: "text"`, or `skip` without a reason the runner gives (`skipReason(vector, target, guard)`, the reason the runner gives: the vector's own `skip.reason` on its `skip.target`, or `not-applicable` for a target it marks unobservable or a `scope` other than `any` and the guard; `not-applicable:no-<origin>-origin` for an origin in `knownOrigins` that the row's target, `plain`, the control's origin, the steps or a check names, directly or through `originDefaults`, where only the first branch of an `any` counts, or `not-supported:<capability>` for a capability the set requires, `browserSession` or `unguarded-observer`); `pass` needs it empty too, and `line` then ends in `forged <n>`.
312
314
 
313
315
  **`npm run validate`** checks that:
314
316
  - the file matches the schema, and ids are unique;
@@ -357,7 +359,7 @@ otherwise they run serially. `capabilities` is an object of booleans or an array
357
359
  of supported names, and a capability that isn't declared is unsupported: a CDP
358
360
  transport over a DevTools port declares `chromeUrlsAttach: true`, and an
359
361
  extension on `chrome.debugger` leaves it out, so `C8.newtab` is skipped with
360
- `not-supported:chromeUrlsAttach`. `capabilities.scope` selects owner-specific rows.
362
+ `not-supported:chromeUrlsAttach`. `capabilities.scope` selects owner-specific rows; pass the same name as `guard` to `summary()`.
361
363
 
362
364
  Map `ordinary`, `ordinary-same-site`, `seven`, `vault`, `vault-x`, `apex`, `own`
363
365
  and `client` to origins. `chrome-newtab`, `chrome-new-tab-page`, `chrome-settings`, `chrome-passwords`, `chrome-search`, `network-error`, `chrome-extension-page` and `file-root` default to their `url`.
package/guard-driver.mjs CHANGED
@@ -1,5 +1,25 @@
1
1
  const q = JSON.stringify;
2
2
 
3
+ export const originDefaults = {
4
+ plain: 'ordinary',
5
+ cross: 'client',
6
+ log: 'ordinary',
7
+ own: 'client',
8
+ };
9
+ export const knownOrigins = [
10
+ 'ordinary',
11
+ 'ordinary-same-site',
12
+ 'seven',
13
+ 'vault',
14
+ 'vault-x',
15
+ 'apex',
16
+ 'own',
17
+ 'client',
18
+ 'plain',
19
+ 'cross',
20
+ 'log',
21
+ ];
22
+
3
23
  export const beacon = (log, tag) =>
4
24
  `try { const u = ${q(log)} + '/beacon?tag=' + ${q(tag)} + '&origin=' + encodeURIComponent(location.origin); if (!navigator.sendBeacon(u)) fetch(u, { keepalive: true, mode: 'no-cors' }).catch(() => {}); } catch {}`;
5
25
 
@@ -276,6 +296,86 @@ const passed = (r) =>
276
296
  [r.control, r.guarded, r.live].every((s) => s === 'skip') ||
277
297
  [r.control, r.guarded, r.live].every((s) => s === 'not-run');
278
298
 
299
+ const capabilities = (value) =>
300
+ Array.isArray(value)
301
+ ? value.flatMap(capabilities)
302
+ : value && typeof value === 'object'
303
+ ? Object.entries(value).flatMap(([key, item]) =>
304
+ key === 'requires' && Array.isArray(item) ? item : capabilities(item)
305
+ )
306
+ : [];
307
+
308
+ const originNames = (value) =>
309
+ [...q(value ?? null).matchAll(/\{([A-Za-z][A-Za-z-]*)\}/g)]
310
+ .map((match) => (match[1] === 'beacon' || match[1] === 'probe' ? 'log' : match[1]))
311
+ .flatMap((name) => [name, originDefaults[name]]);
312
+
313
+ const checkOrigins = (check) => {
314
+ if (!check || typeof check !== 'object') return [];
315
+ if (check.any) return checkOrigins(check.any[0]);
316
+ if (check.all || check.not) return (check.all ?? [check.not]).flatMap(checkOrigins);
317
+ return originNames(check);
318
+ };
319
+
320
+ function originReason(vector, row, reason) {
321
+ const origin = /^not-applicable:no-(.+)-origin$/.exec(reason)?.[1];
322
+ if (!knownOrigins.includes(origin)) return false;
323
+ const fixed = [
324
+ row.target,
325
+ '{plain}',
326
+ vector?.control?.origin,
327
+ vector?.setup,
328
+ vector?.trigger,
329
+ ].map((value) => (typeof value === 'string' && !value.startsWith('{') ? `{${value}}` : value));
330
+ return [
331
+ ...originNames(fixed),
332
+ ...checkOrigins(vector?.liveCheck),
333
+ ...Object.values(vector?.observe ?? {}).flatMap((observation) =>
334
+ checkOrigins(observation?.check)
335
+ ),
336
+ ].includes(origin);
337
+ }
338
+
339
+ export const skipReason = (vector, target, scope) => {
340
+ if (!vector) return null;
341
+ if (vector.skip?.target === target) return vector.skip.reason;
342
+ if (vector.observable?.[target] === false) return 'not-applicable';
343
+ return vector.scope !== 'any' && vector.scope !== scope ? 'not-applicable' : null;
344
+ };
345
+
346
+ export const inapplicable = (vector, target, scope) => skipReason(vector, target, scope) !== null;
347
+
348
+ function skipWhy(vector, row, known, guard) {
349
+ const { reason } = row;
350
+ if (typeof reason !== 'string' || reason === '') return 'skip without a reason';
351
+ if (reason === skipReason(vector, row.target, guard)) return null;
352
+ if (originReason(vector, row, reason)) return null;
353
+ if (reason.startsWith('not-supported:') && known.has(reason.slice('not-supported:'.length)))
354
+ return null;
355
+ return `skip with a reason the runner never gives: ${reason}`;
356
+ }
357
+
358
+ function forgeries(set, results, guard) {
359
+ const vectors = new Map(set.vectors.map((v) => [v.id, v]));
360
+ const known = new Set([
361
+ 'browserSession',
362
+ 'unguarded-observer',
363
+ ...capabilities(set.vectors).filter((c) => typeof c === 'string' && c !== ''),
364
+ ]);
365
+ return results.flatMap((row) => {
366
+ const legs = [row.control, row.guarded, row.live];
367
+ const vector = vectors.get(row.id);
368
+ const why = [];
369
+ if (legs.includes('not-run') && vector?.kind !== 'text')
370
+ why.push('not-run on a vector that is not kind text');
371
+ if (legs.includes('skip')) why.push(skipWhy(vector, row, known, guard));
372
+ const found = why.filter(Boolean);
373
+ if (!found.length) return [];
374
+ const { id, target, variant, transport } = row;
375
+ return [{ id, target, variant, transport, why: found.join('; ') }];
376
+ });
377
+ }
378
+
279
379
  export function summary({ guard, sha, set, results }) {
280
380
  const uniq = (k) => [...new Set(results.map((r) => r[k]))].sort();
281
381
  const variants = [...new Set([...Object.keys(set.variants ?? {}), ...uniq('variant')])].sort();
@@ -286,7 +386,9 @@ export function summary({ guard, sha, set, results }) {
286
386
  .flatMap((row) => variants.map((variant) => ({ ...row, variant })))
287
387
  .flatMap((row) => uniq('transport').map((transport) => ({ ...row, transport })))
288
388
  .filter((r) => !has.has(q([r.id, r.target, r.variant, r.transport])));
289
- const pass = results.length > 0 && missing.length === 0 && results.every(passed);
389
+ const forged = forgeries(set, results, guard);
390
+ const pass =
391
+ results.length > 0 && missing.length === 0 && forged.length === 0 && results.every(passed);
290
392
  const name = `@ai-ecoverse/slicc-browsers@${set.setVersion}`;
291
393
  return {
292
394
  set: name,
@@ -302,8 +404,9 @@ export function summary({ guard, sha, set, results }) {
302
404
  .map(({ id, target, reason }) => ({ id, target, reason })),
303
405
  notRun: [...new Set(results.filter((r) => r.control === 'not-run').map((r) => r.id))],
304
406
  missing,
407
+ forged,
305
408
  pass,
306
- line: `${pass ? 'GUARD-PASS' : 'GUARD-FAIL'} ${guard}@${sha} (vectors ${name})`,
409
+ line: `${pass ? 'GUARD-PASS' : 'GUARD-FAIL'} ${guard}@${sha} (vectors ${name})${forged.length ? ` forged ${forged.length}` : ''}`,
307
410
  };
308
411
  }
309
412
 
package/guard-runner.mjs CHANGED
@@ -8,13 +8,17 @@ import {
8
8
  canary,
9
9
  expand,
10
10
  hop,
11
+ inapplicable,
11
12
  isRefusal,
13
+ knownOrigins,
12
14
  loadedAfter,
15
+ originDefaults,
13
16
  outcome,
14
17
  owned,
15
18
  parseURL,
16
19
  summary as report,
17
20
  result,
21
+ skipReason,
18
22
  steps,
19
23
  waitFor,
20
24
  } from './guard-driver.mjs';
@@ -1287,10 +1291,7 @@ const needsObserver = (vector) =>
1287
1291
  );
1288
1292
 
1289
1293
  const inactive = (vector, target, capabilities) =>
1290
- vector.skip?.target === target ||
1291
- vector.observable?.[target] === false ||
1292
- !supported(vector.requires, capabilities) ||
1293
- (vector.scope !== 'any' && vector.scope !== capabilities.scope);
1294
+ inapplicable(vector, target, capabilities.scope) || !supported(vector.requires, capabilities);
1294
1295
 
1295
1296
  const allSteps = (list) => list.flatMap((step) => [step, ...allSteps(step.children ?? [])]);
1296
1297
  const browserContextStep = (vector, capabilities) =>
@@ -1302,21 +1303,6 @@ const browserContextStep = (vector, capabilities) =>
1302
1303
  supported(['browserContexts'], capabilities))
1303
1304
  );
1304
1305
 
1305
- const originDefaults = { plain: 'ordinary', cross: 'client', log: 'ordinary', own: 'client' };
1306
- const knownOrigins = [
1307
- 'ordinary',
1308
- 'ordinary-same-site',
1309
- 'seven',
1310
- 'vault',
1311
- 'vault-x',
1312
- 'apex',
1313
- 'own',
1314
- 'client',
1315
- 'plain',
1316
- 'cross',
1317
- 'log',
1318
- ];
1319
-
1320
1306
  function nullOrigins(value, targets) {
1321
1307
  return [...q(value).matchAll(/\{([A-Za-z][A-Za-z-]*)\}/g)]
1322
1308
  .map((match) => (match[1] === 'beacon' || match[1] === 'probe' ? 'log' : match[1]))
@@ -1508,7 +1494,9 @@ async function executeRow(job, opts, made, set, slot, index, flights) {
1508
1494
  control: 'skip',
1509
1495
  guarded: 'skip',
1510
1496
  live: 'skip',
1511
- reason: missing ? `not-supported:${missing}` : 'not-applicable',
1497
+ reason: missing
1498
+ ? `not-supported:${missing}`
1499
+ : skipReason(vector, target, opts.capabilities.scope),
1512
1500
  };
1513
1501
  }
1514
1502
  if (
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "setVersion": "1.13.4",
3
+ "setVersion": "1.13.6",
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.13.4",
3
+ "version": "1.13.6",
4
4
  "description": "SLICC's shared browser catalog, JSON schemas and discovery conformance corpus",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -66,7 +66,7 @@
66
66
  "validate": "node scripts/validate.mjs",
67
67
  "lint": "slicc-lint && test -z \"$(gofmt -l .)\" && go mod tidy -diff && go vet ./... && golangci-lint run && cargo fmt --check && cargo clippy --all-features --all-targets -- -D warnings",
68
68
  "format": "biome check --write . && gofmt -w . && cargo fmt",
69
- "test": "npm run validate && node --test test/integration/self-contained.test.mjs test/integration/browser-runner.test.mjs test/integration/fixture-client.test.mjs test/integration/git-retry.test.mjs test/integration/guard-redirect-carrier.test.mjs test/integration/dist-tag.test.mjs test/integration/guard-incomplete.test.mjs test/integration/guard-stall.test.mjs && go test -race -count=1 ./test/integration/... && cargo test --all-features",
69
+ "test": "npm run validate && node --test test/integration/self-contained.test.mjs test/integration/browser-runner.test.mjs test/integration/fixture-client.test.mjs test/integration/git-retry.test.mjs test/integration/guard-redirect-carrier.test.mjs test/integration/dist-tag.test.mjs test/integration/guard-incomplete.test.mjs test/integration/guard-stall.test.mjs test/integration/guard-forged.test.mjs && go test -race -count=1 ./test/integration/... && cargo test --all-features",
70
70
  "test:unit": "go test -race -cover ./...",
71
71
  "build": "go build ./... && cargo build --all-features && cargo build --target wasm32-wasip1 --all-features && npm pack --dry-run",
72
72
  "trust": "npx fledgling sync -y --skip-publish",
@@ -222,8 +222,9 @@ class RedirectProbe {
222
222
  .filter(
223
223
  (event) =>
224
224
  event.method === 'Network.requestWillBeSent' &&
225
- !event.params.redirectResponse &&
226
- event.params.request?.url?.startsWith(this.prefix)
225
+ (event.params.redirectResponse
226
+ ? event.params.redirectResponse.url?.startsWith(this.prefix)
227
+ : event.params.request?.url?.startsWith(this.prefix))
227
228
  )
228
229
  .map((event) => event.params.requestId)
229
230
  );