@ai-ecoverse/slicc-browsers 1.18.8 → 1.18.10

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
@@ -184,6 +184,10 @@ A guard sits between a CDP client, such as an agent's browser tool, and the brow
184
184
 
185
185
  Each vector runs twice. The **control** runs it against an ordinary origin, where it must work. The **guarded** leg runs it through the guard against a protected target, where it must be refused or have no effect. A **liveness** check, made outside the guard, proves the protected target was really there. A row passes only when all three do, so a guard can't pass by breaking everything, and a test can't pass by never reaching the target.
186
186
 
187
+ When a vector compares ordinary and protected fixture markers, those markers do not contain one another. Protected-marker checks still examine complete probe replies, including errors, and the vector's client events and messages, including encoded values.
188
+
189
+ Client-exit controls use the unguarded observer; persistent-capability refusals are valid evidence, granted instruments must leave no effects after exit, and missing answers remain incomplete.
190
+
187
191
  ### Running them
188
192
 
189
193
  The reference runner works with any guard that speaks CDP:
@@ -40,8 +40,8 @@ async function bounded(work, ms) {
40
40
  class Restore {
41
41
  constructor(options) {
42
42
  Object.assign(this, options);
43
- this.marker = `bfcache-marker-${this.tag}`;
44
- this.url = `${this.protectedOrigin}/bfcache?n=${encodeURIComponent(this.tag)}`;
43
+ this.marker = `bfcache-marker-${this.tag}-p`;
44
+ this.url = `${this.protectedOrigin}/bfcache?n=${encodeURIComponent(`${this.tag}-p`)}`;
45
45
  this.result = { probes: [], delay: { requestedMs: this.delayMs } };
46
46
  this.missing = [];
47
47
  this.pending = [];
package/guard-runner.mjs CHANGED
@@ -159,10 +159,14 @@ class Execution {
159
159
 
160
160
  async exit() {
161
161
  const gone = this.client;
162
- if (typeof gone.close !== 'function') throw new Error('the guarded client has no close()');
163
162
  this.exitRequestStart = (await this.options.fixture.log()).length;
164
163
  this.clients.exited = true;
165
- await gone.close();
164
+ if (gone === this.reader)
165
+ await gone.send('Target.detachFromTarget', { sessionId: this.sessions.page });
166
+ else {
167
+ if (typeof gone.close !== 'function') throw new Error('the guarded client has no close()');
168
+ await gone.close();
169
+ }
166
170
  const released = await this.poll(async () => {
167
171
  const read = await outcome(
168
172
  this.reader.send('Target.getTargetInfo', { targetId: this.pageTarget })
@@ -664,8 +668,15 @@ class Execution {
664
668
  refused,
665
669
  ...(gone ? { detached: true } : {}),
666
670
  });
667
- if (step.refusable) this.refused[step.refusal.name] = refused;
668
- if (step.refusable && !out.ok && !refused && !gone)
671
+ const unanswered =
672
+ step.refusable &&
673
+ exitsClient(this.vector) &&
674
+ !out.ok &&
675
+ !refused &&
676
+ /timeout|timed out|no answer/i.test(String(out.error?.message ?? out.error));
677
+ if (step.refusable) this.refused[step.refusal.name] = unanswered ? 'incomplete' : refused;
678
+ if (unanswered) this.incomplete.push(`${step.refusal.name}: no answer`);
679
+ else if (step.refusable && !out.ok && !refused && !gone)
669
680
  this.errors.push(
670
681
  `${step.refusal.name} failed without a refusal: ${String(out.error?.message ?? out.error)}`
671
682
  );
@@ -881,10 +892,11 @@ class Execution {
881
892
  };
882
893
  const met = await this.poll(arrived, settle.ms ?? 1500, absent);
883
894
  if (refused() && !alt) return skipped();
884
- if (settle.required && !met)
885
- this.errors.push(
886
- `settle: no ${settle.beacon ? `beacon from ${settle.beacon}` : settle.served ? `request for ${settle.served}` : settle.title ? `title ${settle.title}` : 'load'} within ${this.bound(settle.ms ?? 1500)}ms`
887
- );
895
+ if (settle.required && !met) {
896
+ const missing = `settle: no ${settle.beacon ? `beacon from ${settle.beacon}` : settle.served ? `request for ${settle.served}` : settle.title ? `title ${settle.title}` : 'load'} within ${this.bound(settle.ms ?? 1500)}ms`;
897
+ if (exitsClient(this.vector)) this.incomplete.push(missing);
898
+ else this.errors.push(missing);
899
+ }
888
900
  }
889
901
 
890
902
  async guess(guess, session) {
@@ -926,7 +938,21 @@ class Execution {
926
938
  session === undefined
927
939
  )
928
940
  params.browserContextId = this.vars.context;
929
- const out = await outcome(this.actor().send(step.cdp, params, session));
941
+ const pending = outcome(this.actor().send(step.cdp, params, session));
942
+ let timer;
943
+ const out =
944
+ step.refusable && exitsClient(this.vector)
945
+ ? await Promise.race([
946
+ pending,
947
+ new Promise((resolve) => {
948
+ timer = setTimeout(
949
+ () => resolve({ ok: false, error: { message: 'no answer' } }),
950
+ this.bound(this.options.namedResultMs ?? namedResultMs)
951
+ );
952
+ }),
953
+ ])
954
+ : await pending;
955
+ clearTimeout(timer);
930
956
  if (step.cdp === 'Target.setAutoAttach' && out.ok) {
931
957
  if (params.autoAttach) this.attachSettings.set(session, true);
932
958
  else this.attachSettings.delete(session);
@@ -1259,8 +1285,17 @@ class Execution {
1259
1285
  observed[name] = 'not-supported';
1260
1286
  continue;
1261
1287
  }
1262
- if ((observation.dependsOn ?? []).some((dep) => this.refused[dep])) continue;
1288
+ const dependencies = observation.dependsOn ?? [];
1289
+ if (dependencies.some((dep) => this.refused[dep] === 'incomplete')) {
1290
+ observed[name] = 'incomplete';
1291
+ continue;
1292
+ }
1293
+ if (dependencies.some((dep) => this.refused[dep] === true)) continue;
1263
1294
  observed[name] = await this.observe(name, observation.check);
1295
+ if (exitsClient(this.vector) && dependencies.length && observed[name] === false) {
1296
+ observed[name] = 'incomplete';
1297
+ this.incomplete.push(`${name}: pre-exit evidence not observed`);
1298
+ }
1264
1299
  if (!observation.eitherWhen || this.side !== 'guarded') continue;
1265
1300
  const holds = await this.either(observation.eitherWhen, observed[name], name, unseen);
1266
1301
  observed[`${name}.either`] = holds;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "setVersion": "1.18.8",
3
+ "setVersion": "1.18.10",
4
4
  "sources": [
5
5
  {
6
6
  "repo": "ai-ecoverse/slicc-cli",
@@ -18804,7 +18804,7 @@
18804
18804
  "requires": ["clientExit"],
18805
18805
  "setup": [
18806
18806
  {
18807
- "text": "a plain page whose client registers an init script, an x-probe header and Fetch.enable '*' that fulfils the destination with a forged body, then loads a trigger page whose navigation into {target} is pending when the client exits"
18807
+ "text": "a plain page registers an init script, an x-probe header and Fetch.enable '*' that fulfils the destination with a forged body, then loads a trigger page whose navigation into {target} is pending when the session exits; the control uses the unguarded observer, and guarded refusals waive only their corresponding pre-exit proofs"
18808
18808
  },
18809
18809
  {
18810
18810
  "page": "tab",
@@ -18814,6 +18814,11 @@
18814
18814
  "cdp": "Page.addScriptToEvaluateOnNewDocument",
18815
18815
  "params": {
18816
18816
  "source": "{beacon}"
18817
+ },
18818
+ "as": "initScript",
18819
+ "refusable": true,
18820
+ "refusal": {
18821
+ "match": "refused"
18817
18822
  }
18818
18823
  },
18819
18824
  {
@@ -18822,6 +18827,11 @@
18822
18827
  "headers": {
18823
18828
  "x-probe": "{tag}"
18824
18829
  }
18830
+ },
18831
+ "as": "extraHeaders",
18832
+ "refusable": true,
18833
+ "refusal": {
18834
+ "match": "refused"
18825
18835
  }
18826
18836
  },
18827
18837
  {
@@ -18832,6 +18842,11 @@
18832
18842
  "urlPattern": "*"
18833
18843
  }
18834
18844
  ]
18845
+ },
18846
+ "as": "interception",
18847
+ "refusable": true,
18848
+ "refusal": {
18849
+ "match": "refused"
18835
18850
  }
18836
18851
  },
18837
18852
  {
@@ -18860,7 +18875,12 @@
18860
18875
  {
18861
18876
  "settle": {
18862
18877
  "beacon": "{ordinary}",
18863
- "required": true
18878
+ "required": true,
18879
+ "dependsOn": ["initScript"],
18880
+ "or": {
18881
+ "served": "{ordinary}/page?k=href&tag={tag}&to={target}/canary/{tag}",
18882
+ "observer": "fixture"
18883
+ }
18864
18884
  }
18865
18885
  },
18866
18886
  {
@@ -18880,7 +18900,8 @@
18880
18900
  "targets": ["seven", "vault"],
18881
18901
  "observe": {
18882
18902
  "instrumented": {
18883
- "means": "the client's init script sent its beacon from {ordinary} before the exit",
18903
+ "means": "the session's init script sent its beacon from {ordinary} before the exit unless registration was refused",
18904
+ "dependsOn": ["initScript"],
18884
18905
  "check": {
18885
18906
  "beacon": "{ordinary}",
18886
18907
  "exit": "before",
@@ -18888,7 +18909,8 @@
18888
18909
  }
18889
18910
  },
18890
18911
  "headed": {
18891
- "means": "the trigger page's request carried the client's x-probe header before the exit",
18912
+ "means": "the trigger page's request carried the session's x-probe header before the exit unless registration was refused",
18913
+ "dependsOn": ["extraHeaders"],
18892
18914
  "check": {
18893
18915
  "header": "x-probe",
18894
18916
  "on": "/page",
@@ -18897,7 +18919,8 @@
18897
18919
  }
18898
18920
  },
18899
18921
  "intercepted": {
18900
- "means": "a Fetch.requestPaused for the trigger page reached the client before the exit",
18922
+ "means": "a Fetch.requestPaused for the trigger page reached the session before the exit unless interception was refused",
18923
+ "dependsOn": ["interception"],
18901
18924
  "check": {
18902
18925
  "event": "Fetch.requestPaused",
18903
18926
  "where": {
@@ -18914,6 +18937,7 @@
18914
18937
  },
18915
18938
  "served": {
18916
18939
  "means": "the stand-in served {target}/canary/{tag} after the exit",
18940
+ "dependsOn": [],
18917
18941
  "check": {
18918
18942
  "served": "{target}/canary/{tag}",
18919
18943
  "exit": "after",
@@ -18922,6 +18946,7 @@
18922
18946
  },
18923
18947
  "document": {
18924
18948
  "means": "the unguarded observer reads {target}'s real document, title 'canary'",
18949
+ "dependsOn": [],
18925
18950
  "check": {
18926
18951
  "title": "canary",
18927
18952
  "via": "unguarded",
@@ -18930,6 +18955,7 @@
18930
18955
  },
18931
18956
  "forged": {
18932
18957
  "means": "the unguarded observer reads the client's forged FULFILLED body",
18958
+ "dependsOn": [],
18933
18959
  "check": {
18934
18960
  "expression": "document.body ? document.body.innerText.includes('FULFILLED') : false",
18935
18961
  "equals": true,
@@ -18939,6 +18965,7 @@
18939
18965
  },
18940
18966
  "scripted": {
18941
18967
  "means": "the client's init script sent a beacon from {target} after the exit",
18968
+ "dependsOn": [],
18942
18969
  "check": {
18943
18970
  "beacon": "{target}",
18944
18971
  "exit": "after",
@@ -18947,6 +18974,7 @@
18947
18974
  },
18948
18975
  "probed": {
18949
18976
  "means": "a request for {target}/canary/{tag} carried the client's x-probe header after the exit",
18977
+ "dependsOn": [],
18950
18978
  "check": {
18951
18979
  "header": "x-probe",
18952
18980
  "on": "{target}/canary/{tag}",
@@ -18956,6 +18984,7 @@
18956
18984
  },
18957
18985
  "paused": {
18958
18986
  "means": "a Fetch.requestPaused for {target}/canary/{tag} reached the client",
18987
+ "dependsOn": [],
18959
18988
  "check": {
18960
18989
  "event": "Fetch.requestPaused",
18961
18990
  "where": {
@@ -18969,10 +18998,10 @@
18969
18998
  }
18970
18999
  }
18971
19000
  },
18972
- "live": "the stand-in serves {target}'s document after the client exits",
19001
+ "live": "the stand-in serves {target}'s document after the session exits",
18973
19002
  "control": {
18974
19003
  "origin": "ordinary",
18975
- "via": "guard",
19004
+ "via": "unguarded",
18976
19005
  "expected": {
18977
19006
  "instrumented": true,
18978
19007
  "headed": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-browsers",
3
- "version": "1.18.8",
3
+ "version": "1.18.10",
4
4
  "description": "SLICC's shared browser catalog, JSON schemas and discovery conformance corpus",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -68,7 +68,7 @@
68
68
  "validate": "node scripts/validate.mjs",
69
69
  "lint": "slicc-lint && test -z \"$(gofmt -l .)\" && go mod tidy -diff && go vet ./... && GOOS=darwin go vet ./... && GOOS=windows go vet ./... && golangci-lint run && cargo fmt --check && cargo clippy --all-features --all-targets -- -D warnings && cargo check --target wasm32-wasip1 --no-default-features && cargo check --target wasm32-wasip1 --features serde && cargo check --target wasm32-wasip1 --all-features",
70
70
  "format": "biome check --write . && gofmt -w . && cargo fmt",
71
- "test": "export PATH=\"$HOME/.cargo/bin:$PATH\" && node --test --test-concurrency=1 test/integration/self-contained.test.mjs test/integration/browser-runner.test.mjs test/integration/fixture-client.test.mjs test/integration/guard-redirect-carrier.test.mjs test/integration/guard-redirect-session-end.test.mjs test/integration/guard-redirect-history.test.mjs test/integration/guard-redirect-iframe.test.mjs test/integration/guard-incomplete.test.mjs test/integration/guard-stall.test.mjs test/integration/guard-forged.test.mjs test/integration/guard-exit-steps.test.mjs test/integration/guard-frame-read.test.mjs test/integration/guard-bfcache.test.mjs test/integration/guard-bfcache-history.test.mjs test/integration/guard-bfcache-review.test.mjs test/integration/guard-bfcache-quarantine.test.mjs test/integration/guard-error-page-navigation.test.mjs test/integration/gates.test.mjs test/integration/validate.test.mjs test/integration/go.test.mjs test/integration/cargo.test.mjs test/integration/workflows.test.mjs test/integration/guard-sweep-union.test.mjs && 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-redirect-secret.test.mjs test/integration/guard-error-page.test.mjs test/integration/guard-discovery.test.mjs test/integration/guard-bfcache-chrome.test.mjs test/integration/guard-browser-page.test.mjs test/integration/guard-scales.test.mjs test/integration/guard-shared-log.test.mjs test/integration/guard-pre-beacon.test.mjs test/integration/guard-world-pause.test.mjs test/integration/guard-parallel.test.mjs test/integration/guard-client-exit.test.mjs test/integration/guard-runner.test.mjs test/integration/guard-sweep-ordinary.test.mjs test/integration/guard-sweep-seven.test.mjs test/integration/guard-sweep-vault.test.mjs test/integration/guard-sweep-vault-x.test.mjs test/integration/guard-sweep-apex.test.mjs test/integration/guard-sweep-others.test.mjs",
71
+ "test": "export PATH=\"$HOME/.cargo/bin:$PATH\" && node --test --test-concurrency=1 test/integration/self-contained.test.mjs test/integration/browser-runner.test.mjs test/integration/fixture-client.test.mjs test/integration/guard-redirect-carrier.test.mjs test/integration/guard-redirect-session-end.test.mjs test/integration/guard-redirect-history.test.mjs test/integration/guard-redirect-iframe.test.mjs test/integration/guard-incomplete.test.mjs test/integration/guard-stall.test.mjs test/integration/guard-forged.test.mjs test/integration/guard-exit-steps.test.mjs test/integration/guard-exit-refusal.test.mjs test/integration/guard-frame-read.test.mjs test/integration/guard-bfcache.test.mjs test/integration/guard-bfcache-marker.test.mjs test/integration/guard-bfcache-history.test.mjs test/integration/guard-bfcache-review.test.mjs test/integration/guard-bfcache-quarantine.test.mjs test/integration/guard-error-page-navigation.test.mjs test/integration/gates.test.mjs test/integration/validate.test.mjs test/integration/go.test.mjs test/integration/cargo.test.mjs test/integration/workflows.test.mjs test/integration/guard-sweep-union.test.mjs && 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-redirect-secret.test.mjs test/integration/guard-error-page.test.mjs test/integration/guard-discovery.test.mjs test/integration/guard-bfcache-chrome.test.mjs test/integration/guard-browser-page.test.mjs test/integration/guard-scales.test.mjs test/integration/guard-shared-log.test.mjs test/integration/guard-pre-beacon.test.mjs test/integration/guard-world-pause.test.mjs test/integration/guard-parallel.test.mjs test/integration/guard-client-exit.test.mjs test/integration/guard-runner.test.mjs test/integration/guard-sweep-ordinary.test.mjs test/integration/guard-sweep-seven.test.mjs test/integration/guard-sweep-vault.test.mjs test/integration/guard-sweep-vault-x.test.mjs test/integration/guard-sweep-apex.test.mjs test/integration/guard-sweep-others.test.mjs",
72
72
  "test:unit": "mkdir -p coverage/unit && node --experimental-test-coverage --test --test-concurrency=1 --test-reporter=lcov --test-reporter-destination=coverage/unit/lcov.info test/unit/*.test.mjs && go test -race -cover ./...",
73
73
  "build": "go build ./... && cargo build --all-features && cargo build --target wasm32-wasip1 --all-features && npm pack --dry-run",
74
74
  "prepublishOnly": "node scripts/set-crate-version.mjs \"$npm_package_version\"",
@@ -79,8 +79,8 @@
79
79
  "test:unit:guard": "mkdir -p coverage/unit && node --experimental-vm-modules --no-warnings --experimental-test-coverage --test --test-concurrency=1 --test-coverage-include=guard-runner.mjs --test-coverage-include=fixture-server.mjs --test-coverage-include=fixture-client.mjs --test-coverage-include=observer-target.mjs --test-coverage-include=frame-read.mjs --test-coverage-include=bfcache-restore.mjs --test-coverage-include=protected-error-page.mjs --test-coverage-include=unparseable-url.mjs --test-coverage-include=scripts/browser-runner.mjs --test-coverage-include=scripts/validate.mjs --test-reporter=lcov --test-reporter-destination=coverage/unit/guard.lcov test/unit/guard-*.test.mjs && slicc-diff-cover coverage/unit/guard.lcov"
80
80
  },
81
81
  "devDependencies": {
82
- "@ai-ecoverse/slicc-cdp": "1.31.2",
83
- "@ai-ecoverse/slicc-shared-web": "2.8.4",
82
+ "@ai-ecoverse/slicc-cdp": "1.31.9",
83
+ "@ai-ecoverse/slicc-shared-web": "2.10.4",
84
84
  "@biomejs/biome": "2.5.15",
85
85
  "ajv": "8.20.0",
86
86
  "ajv-formats": "3.0.1",