@ai-ecoverse/slicc-browsers 1.7.1 → 1.7.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
@@ -307,7 +307,13 @@ observations: a missing or failing effect fails the control. Guarded liveness
307
307
  requires the exact target URL in the fixture log or a target-origin `/live`
308
308
  heartbeat; alternatively a pre-action `/live?tag=...&step=pre&to=...` request and
309
309
  an exact-URL block/error from the client or unguarded frame tree. An error for a
310
- different URL is not evidence. A declared setup refusal can supply liveness only
310
+ different URL is not evidence. `blocked` accepts either `{ url }` for one exact URL
311
+ or `{ anyOf: [url, ...] }` for a nonempty list of exact URLs; each URL can be a
312
+ string or `{ hops, to, index? }`. Both forms allow `session` and `frame`. Redirect
313
+ vectors list every nested hop URL and the final destination because Chromium
314
+ reports the URL intercepted by the guard. A matching block still requires the
315
+ pre-action beacon alongside it; an unrelated error cannot certify the chain.
316
+ A declared setup refusal can supply liveness only
311
317
  when its `refusal.match` matched. Transport connectivity and command completion
312
318
  alone never prove liveness. Cookie and storage rows seed a canary outside the
313
319
  guard and read it back outside the guard; refused writes require unchanged
package/guard-runner.mjs CHANGED
@@ -245,7 +245,7 @@ class Execution {
245
245
  }
246
246
 
247
247
  async blocked(spec, observer) {
248
- const url = address(spec.url);
248
+ const urls = (spec.anyOf ?? [spec.url]).map(address);
249
249
  const events = this.client.events.slice(this.eventStart);
250
250
  if (observer === 'client') {
251
251
  const requests = new Map(
@@ -259,10 +259,10 @@ class Execution {
259
259
  (!spec.frame ||
260
260
  event.params.frame.parentId ||
261
261
  event.sessionId === this.sessions.child) &&
262
- event.params.frame.unreachableUrl === url &&
262
+ urls.includes(event.params.frame.unreachableUrl) &&
263
263
  event.params.frame.url.startsWith('chrome-error://')) ||
264
264
  (event.method === 'Network.loadingFailed' &&
265
- requests.get(event.params.requestId) === url &&
265
+ urls.includes(requests.get(event.params.requestId)) &&
266
266
  (event.params.blockedReason || event.params.errorText === 'net::ERR_BLOCKED_BY_CLIENT'))
267
267
  );
268
268
  }
@@ -279,7 +279,8 @@ class Execution {
279
279
  const visit = (tree) =>
280
280
  Boolean(
281
281
  tree &&
282
- ((tree.frame.unreachableUrl === url && tree.frame.url.startsWith('chrome-error://')) ||
282
+ ((urls.includes(tree.frame.unreachableUrl) &&
283
+ tree.frame.url.startsWith('chrome-error://')) ||
283
284
  (tree.childFrames ?? []).some(visit))
284
285
  );
285
286
  return (
@@ -624,6 +625,7 @@ class Execution {
624
625
  );
625
626
  if (c.refused || c.reached) return this.callCheck(c);
626
627
  if (c.detached) {
628
+ const events = this.client.events.slice(this.eventStart, this.cleanupEventStart);
627
629
  const session = this.sessions[c.detached];
628
630
  return (
629
631
  Boolean(session) &&
@@ -938,6 +940,27 @@ async function executeRow(job, opts, clients, set, slot, index) {
938
940
  return row;
939
941
  }
940
942
 
943
+ const invalidBlocked = (check) =>
944
+ Boolean(
945
+ check?.blocked &&
946
+ ((check.blocked.url === undefined) === (check.blocked.anyOf === undefined) ||
947
+ (check.blocked.anyOf !== undefined &&
948
+ (!Array.isArray(check.blocked.anyOf) || check.blocked.anyOf.length === 0)))
949
+ ) ||
950
+ [...(check?.all ?? []), ...(check?.any ?? []), ...(check?.not ? [check.not] : [])].some(
951
+ invalidBlocked
952
+ );
953
+
954
+ function validateBlocked(vectors) {
955
+ for (const vector of vectors)
956
+ if (
957
+ [vector.liveCheck, ...Object.values(vector.observe ?? {}).map((o) => o.check)].some(
958
+ invalidBlocked
959
+ )
960
+ )
961
+ throw new TypeError(`${vector.id}: blocked needs exactly one url or nonempty anyOf`);
962
+ }
963
+
941
964
  function validate(set, opts) {
942
965
  if (!opts.fixture?.log) throw new TypeError('fixture.log is required');
943
966
  for (const vector of set.vectors)
@@ -954,6 +977,7 @@ function validate(set, opts) {
954
977
  for (const vector of set.vectors)
955
978
  if (vector.kind === 'cdp' && invalidLive(vector.liveCheck))
956
979
  throw new TypeError(`${vector.id}: liveCheck needs guarded attempt evidence`);
980
+ validateBlocked(set.vectors);
957
981
  const variants = Array.isArray(opts.variant) ? opts.variant : [opts.variant];
958
982
  for (const name of variants)
959
983
  if (!(name in set.variants)) throw new TypeError(`Unknown variant: ${name}`);