@ai-ecoverse/slicc-browsers 1.7.3 → 1.7.5
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 +26 -10
- package/catalog.json +1 -1
- package/fixture-server.mjs +4 -2
- package/guard-driver.mjs +25 -7
- package/guard-runner.mjs +239 -75
- package/guard-vectors.json +85 -2
- package/package.json +5 -3
- package/protected.json +4 -0
- package/schema/guard-vectors.schema.json +30 -0
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ Every feature builds for `wasm32-wasip1`.
|
|
|
87
87
|
- `names`: an endpoint's name when it isn't from a known browser's user-data dir: `electron` when the user agent contains `electronUserAgent`, else `products[<product>]` for the product before the `/` in `/json/version`'s `Browser`, else that product, else `fallback`;
|
|
88
88
|
- `process`: the process scan, see [The process source](#the-process-source).
|
|
89
89
|
- `seven`:
|
|
90
|
-
- `originScheme` and `originHostPattern`: a seven origin is `https://<host>` with a host matching the pattern, except for the `excludedLabels` (`www`, `vault`, `vault-preview`, `get`, `mcpcat`) and the `excludedLabelPrefixes` (`vault-`, which excludes `vault-feature-x` and any other vault branch preview). The bare apex (`sliccy.ai`, i.e. an empty label) never matches. An origin is compared without the scheme's default port (443 for `https`, 80 for `http`), so `https://seven.sliccy.ai:443` is seven and `https://seven.sliccy.ai:8443` is not. The same goes for extra seven origins;
|
|
90
|
+
- `originScheme` and `originHostPattern`: a seven origin is `https://<host>` with a host matching the pattern, except for the `excludedLabels` (`www`, `vault`, `vault-preview`, `get`, `mcpcat`, `echo`) and the `excludedLabelPrefixes` (`vault-`, which excludes `vault-feature-x` and any other vault branch preview). The bare apex (`sliccy.ai`, i.e. an empty label) never matches. An origin is compared without the scheme's default port (443 for `https`, 80 for `http`), so `https://seven.sliccy.ai:443` is seven and `https://seven.sliccy.ai:8443` is not. The same goes for extra seven origins;
|
|
91
91
|
- `sitePattern` (`[*.]sliccy.ai`);
|
|
92
92
|
- `switches` and `disabledFeatures`: the seven flags, all `--disable-features` values merged into one switch;
|
|
93
93
|
- `prefs`: values merged into a profile's `Default/Preferences` at the `path` of keys;
|
|
@@ -112,7 +112,7 @@ Browser control must never navigate to, open or attach to a target on seven's ow
|
|
|
112
112
|
3. A scheme in `schemes` is protected when its host is one of `hosts`, or one of `domains` or under one, at any depth and on any port. The host is lower-cased, and one trailing dot is dropped.
|
|
113
113
|
4. Any other URL is protected when its origin is one of the program's **extra origins**: the seven origins it accepts besides `sliccy.ai` (slicc-cli's `--origin` and `--page` origins), compared as `scheme://host[:port]` without the scheme's default port, with the host as in 3.
|
|
114
114
|
|
|
115
|
-
This is wider than the rule for finding seven (`seven.originHostPattern`, which leaves out the `excludedLabels` and `excludedLabelPrefixes`—`www`, `vault`, `vault-preview`, `get`, `mcpcat`, and any `vault-*` prefix). Every `sliccy.ai` host shares the `sliccy.ai` cookies, and cookies ignore the port and, without `Secure`, the scheme. SLICC six lives on `www.sliccy.ai` and the apex, and its cookies matter. `*.kernel.localhost` is not protected: each port is its own origin, and the agent's own servers live there. An extra origin is matched exactly, so `http://127.0.0.1:5710` doesn't protect `http://127.0.0.1:5711`.
|
|
115
|
+
This is wider than the rule for finding seven (`seven.originHostPattern`, which leaves out the `excludedLabels` and `excludedLabelPrefixes`—`www`, `vault`, `vault-preview`, `get`, `mcpcat`, `echo`, and any `vault-*` prefix). Every `sliccy.ai` host shares the `sliccy.ai` cookies, and cookies ignore the port and, without `Secure`, the scheme. SLICC six lives on `www.sliccy.ai` and the apex, and its cookies matter. `*.kernel.localhost` is not protected: each port is its own origin, and the agent's own servers live there. An extra origin is matched exactly, so `http://127.0.0.1:5710` doesn't protect `http://127.0.0.1:5711`.
|
|
116
116
|
|
|
117
117
|
**The vault.** `vault.sliccy.ai` is in `hosts` by name, and vault branch previews (`vault-preview.sliccy.ai`, `vault-*.sliccy.ai`) are covered by the `sliccy.ai` domain rule. `sliccy.ai` already covers them all, but vault must stay protected even if `domains` ever changes: the vault must never be a CDP target for agent tools. The one exception is slicc-cli's own pairing attach for the vault, which is the CLI's own connection and not browser control handed to the agent.
|
|
118
118
|
|
|
@@ -150,7 +150,7 @@ Every CDP guard runs the vectors in `guard-vectors.json` (schema `schema/guard-v
|
|
|
150
150
|
- `scope`: `any`, or the one guard the vector is for;
|
|
151
151
|
- `setup` and `trigger`, both lists of steps;
|
|
152
152
|
- `targets`: the protected targets it runs against;
|
|
153
|
-
- `requires?`: the capabilities it needs, the union of its steps' and observations' `requires`. A runner whose transport lacks one reports the vector as `skip` with the reason `not-supported:<capability
|
|
153
|
+
- `requires?`: the capabilities it needs, the union of its steps' and observations' `requires`. A runner whose transport lacks one reports the vector as `skip` with the reason `not-supported:<capability>`. A row with a `page-or-browser` cookie step that would carry a `browserContextId` reports `not-supported:browserSession` without a browser session, because that parameter is only allowed on the browser target;
|
|
154
154
|
- `observe?`: each observed name, as `{ "means", "dependsOn"?, "requires"?, "eitherWhen"? }`. `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);
|
|
155
155
|
- `live`: the liveness check;
|
|
156
156
|
- `control` (`origin: "ordinary"`, `via`, `expected`) and `expected`: an object of names and booleans, or text;
|
|
@@ -170,7 +170,7 @@ Any `cdp`, `page` or `children` step can take `requires`, the capabilities it ne
|
|
|
170
170
|
- `eval`: evaluate `expression`.
|
|
171
171
|
`hops` routes `url` through `/hop` on each `origin`, with its `code`.
|
|
172
172
|
- `{ "answer": { "url", "stage"?, "cdp"?, "params"?, "hold"? } }`: answer each paused request or auth challenge for `url` with that call; `hold` leaves it paused. Every other paused request continues unchanged.
|
|
173
|
-
- `{ "children": steps }`: run these steps on each target auto-attached from then on, then resume it.
|
|
173
|
+
- `{ "children": steps, "where"? }`: run these steps on each target auto-attached from then on, then resume it. `where` scopes them to the row's own target: `{ "openerId": "page" }` (opened by the row's page) or `{ "urlPrefix": url }`; other attached targets are resumed and otherwise left alone.
|
|
174
174
|
- `{ "settle": { "beacon"?, "title"?, "ms"?, "variant"?, "session"? } }`: wait for a beacon from `beacon`, for the page `title`, or for the session to detach. It waits up to `ms` (10000 by default), then another 600 ms. `variant` waits the run's detach variant instead.
|
|
175
175
|
- `{ "wait": ms }`.
|
|
176
176
|
- `{ "guess": { "between", "cdp", "params" }, "as" }`: the call for each request id between the ids paused for the two URLs.
|
|
@@ -253,7 +253,10 @@ error as `error.cdp`, or an error message. `client` carries the guarded leg;
|
|
|
253
253
|
`unguarded`, when supplied, carries unguarded controls and observation reads.
|
|
254
254
|
There is no fallback from observer reads to the guarded client. A bare client
|
|
255
255
|
means guarded transport only; rows requiring an observer report
|
|
256
|
-
`skip` with `not-supported:unguarded-observer`, counted in `summary().observerSkipped`.
|
|
256
|
+
`skip` with `not-supported:unguarded-observer`, counted in `summary().observerSkipped`. The run's `summary()` also reports
|
|
257
|
+
`skips`, the number of skipped rows per reason, and appends
|
|
258
|
+
`skips <n> (<reason>=<count>, ...)` to `line` whenever any row was skipped, so a
|
|
259
|
+
certification line never reads skipped rows as coverage.
|
|
257
260
|
`clientFactory({ variant, detachDelay, vector, target, slot })` is called per row
|
|
258
261
|
and may return either a client or `{ client, unguarded, close, transportVariant }`.
|
|
259
262
|
The factory can configure the guard's own transport delay from this metadata;
|
|
@@ -270,13 +273,16 @@ of supported names. `capabilities.scope` selects owner-specific rows.
|
|
|
270
273
|
Map `ordinary`, `ordinary-same-site`, `seven`, `vault`, `vault-x`, `apex`, `own`
|
|
271
274
|
and `client` to origins. Optionally map `plain`, `cross` and `log`; their defaults
|
|
272
275
|
are `ordinary`, `client` and `ordinary`. Origins must preserve the site
|
|
273
|
-
relationships of the data.
|
|
276
|
+
relationships of the data. Map an unavailable target to `null`, or leave it out; a
|
|
277
|
+
missing or `undefined` origin is treated exactly like `null`: every
|
|
274
278
|
row needing that origin is reported as `skip` for control, guarded and live, with
|
|
275
279
|
reason `not-applicable:no-<target>-origin`; no substitute origin is used and the
|
|
276
280
|
row never passes. A guard whose whole site is protected, such as the extension
|
|
277
281
|
where every `sliccy.ai` host is protected, passes `ordinary-same-site: null`.
|
|
278
|
-
This also applies to origins referenced in setup
|
|
279
|
-
|
|
282
|
+
This also applies to origins referenced in setup and triggers, not only the row
|
|
283
|
+
target. In observations and liveness checks, an `any` alternative that references
|
|
284
|
+
a `null` origin is dropped and the rest are evaluated; the row skips only when
|
|
285
|
+
every alternative of an `any`, or a required (`all`, `not` or plain) check, needs it. Tags and cookie names are unique per leg. `variant`
|
|
280
286
|
selects one variant or an array; omission selects all. `parallel` defaults to 1.
|
|
281
287
|
`log(row)` receives completed rows. A variant supplies the wait before an
|
|
282
288
|
explicit `Target.detachFromTarget` step and for a `settle.variant` step.
|
|
@@ -322,7 +328,17 @@ A step that the guard refuses in every context, such as `Network.clearBrowserCoo
|
|
|
322
328
|
in `C6.clear-cookies`, is refusable, so refusing it on both legs passes and allowing it
|
|
323
329
|
is judged on its effect. A child step that fails after its child session detached is
|
|
324
330
|
recorded as detached, not as a refusal, and the runner awaits a child's started
|
|
325
|
-
`children` steps before any later `session: "child"` step and before the trigger.
|
|
331
|
+
`children` steps before any later `session: "child"` step and before the trigger. A page
|
|
332
|
+
navigation waits for the load of its own `loaderId` (its `Page.frameNavigated` and then a
|
|
333
|
+
load event), and a settle waits for a main-frame commit followed by its load, so a stale
|
|
334
|
+
load event never ends a wait. After a child's `children` steps that include `Page.enable`,
|
|
335
|
+
the runner waits, bounded at 5 s, for the child's first document to commit and load before
|
|
336
|
+
any later child step. `{ "settle": { "beacon", "required": true, "dependsOn"? } }` waits
|
|
337
|
+
for that beacon at the fixture and fails the row with `settle: no beacon from <origin>
|
|
338
|
+
within <ms>ms` when it never arrives, unless a refusal it `dependsOn` was recorded; the rows
|
|
339
|
+
whose `allowedBeacon` comes from an earlier document settle on it before their trigger.
|
|
340
|
+
Beacons use `navigator.sendBeacon`, with a `fetch(…, { keepalive: true })` fallback. An observation that reads a background step's `as` result
|
|
341
|
+
waits for that step, up to 10 s, and fails with `<as> did not complete within …` past it. Redirect
|
|
326
342
|
vectors list every nested hop URL and the final destination because Chromium
|
|
327
343
|
reports the URL intercepted by the guard. A matching block still requires the
|
|
328
344
|
pre-action beacon alongside it; an unrelated error cannot certify the chain.
|
|
@@ -344,7 +360,7 @@ fixture log. A setup beacon on the same origin and tag cannot settle an unfinish
|
|
|
344
360
|
redirect chain. This does not change the observations used to judge either leg.
|
|
345
361
|
|
|
346
362
|
The separately exported Node module `@ai-ecoverse/slicc-browsers/fixture` exposes
|
|
347
|
-
`fixtureServer({ host, port, tls })`, defaulting to a random loopback port.
|
|
363
|
+
`fixtureServer({ host, port, tls, latency })`, defaulting to a random loopback port. `latency(url)` returns milliseconds to hold that response, for timing tests.
|
|
348
364
|
Pass `tls: { key, cert }` to serve HTTPS using Node TLS key and certificate
|
|
349
365
|
values. The returned origin, request logs, redirect URLs and document heartbeats
|
|
350
366
|
use `https://<Host>` with TLS, so `served`, `hops` and `liveCheck` compare against
|
package/catalog.json
CHANGED
|
@@ -642,7 +642,7 @@
|
|
|
642
642
|
"seven": {
|
|
643
643
|
"originScheme": "https",
|
|
644
644
|
"originHostPattern": "^([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)\\.sliccy\\.ai$",
|
|
645
|
-
"excludedLabels": ["www", "vault", "vault-preview", "get", "mcpcat"],
|
|
645
|
+
"excludedLabels": ["www", "vault", "vault-preview", "get", "mcpcat", "echo"],
|
|
646
646
|
"excludedLabelPrefixes": ["vault-"],
|
|
647
647
|
"sitePattern": "[*.]sliccy.ai",
|
|
648
648
|
"switches": [
|
package/fixture-server.mjs
CHANGED
|
@@ -76,7 +76,7 @@ function route(req, url, send, html) {
|
|
|
76
76
|
return send(404, 'missing', 'text/plain');
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
export async function fixtureServer({ host = '127.0.0.1', port = 0, tls } = {}) {
|
|
79
|
+
export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency } = {}) {
|
|
80
80
|
let entries = [];
|
|
81
81
|
const scheme = tls ? 'https' : 'http';
|
|
82
82
|
const handler = (req, res) => {
|
|
@@ -105,7 +105,9 @@ export async function fixtureServer({ host = '127.0.0.1', port = 0, tls } = {})
|
|
|
105
105
|
'text/html',
|
|
106
106
|
headers
|
|
107
107
|
);
|
|
108
|
-
|
|
108
|
+
const wait = latency?.(url) ?? 0;
|
|
109
|
+
if (wait > 0) setTimeout(() => route(req, url, send, html), wait);
|
|
110
|
+
else route(req, url, send, html);
|
|
109
111
|
};
|
|
110
112
|
const server = tls ? createSecureServer(tls, handler) : createServer(handler);
|
|
111
113
|
await new Promise((resolve, reject) => {
|
package/guard-driver.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const q = JSON.stringify;
|
|
2
2
|
|
|
3
3
|
export const beacon = (log, tag) =>
|
|
4
|
-
`try {
|
|
4
|
+
`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
5
|
|
|
6
6
|
export const probe = (log, tag) =>
|
|
7
7
|
`(() => { ${beacon(log, tag)} const p = btoa(location.origin + location.pathname); if (window.probeBinding) window.probeBinding(p); console.log('probe:' + p); })()`;
|
|
@@ -91,13 +91,31 @@ export async function instrument(client, sessionId, log, tag) {
|
|
|
91
91
|
await client.send('Page.setBypassCSP', { enabled: true }, sessionId);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export
|
|
95
|
-
const
|
|
96
|
-
(e) =>
|
|
97
|
-
|
|
94
|
+
export function loadedAfter(events, sessionId, loaderId) {
|
|
95
|
+
const committed = events.findIndex(
|
|
96
|
+
(e) =>
|
|
97
|
+
e.method === 'Page.frameNavigated' &&
|
|
98
|
+
e.sessionId === sessionId &&
|
|
99
|
+
!e.params?.frame?.parentId &&
|
|
100
|
+
e.params?.frame?.loaderId === loaderId
|
|
101
|
+
);
|
|
102
|
+
return (
|
|
103
|
+
committed >= 0 &&
|
|
104
|
+
events
|
|
105
|
+
.slice(committed + 1)
|
|
106
|
+
.some((e) => e.method === 'Page.loadEventFired' && e.sessionId === sessionId)
|
|
98
107
|
);
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function load(client, sessionId, url, ms = 20000) {
|
|
111
|
+
const start = client.events.length;
|
|
112
|
+
const { loaderId } = await client.send('Page.navigate', { url }, sessionId);
|
|
113
|
+
if (!loaderId) return;
|
|
114
|
+
const deadline = Date.now() + ms;
|
|
115
|
+
while (!loadedAfter(client.events.slice(start), sessionId, loaderId)) {
|
|
116
|
+
if (Date.now() > deadline) throw new Error(`load of ${url} timed out`);
|
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
118
|
+
}
|
|
101
119
|
}
|
|
102
120
|
|
|
103
121
|
export function popup(client, openerId, ms = 10000) {
|
package/guard-runner.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
expand,
|
|
5
5
|
hop,
|
|
6
6
|
isRefusal,
|
|
7
|
+
loadedAfter,
|
|
7
8
|
outcome,
|
|
8
9
|
summary as report,
|
|
9
10
|
result,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
} from './guard-driver.mjs';
|
|
12
13
|
|
|
13
14
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
15
|
+
const namedResultMs = 10000;
|
|
14
16
|
const q = JSON.stringify;
|
|
15
17
|
const at = (value, path = '') =>
|
|
16
18
|
path
|
|
@@ -109,6 +111,7 @@ class Execution {
|
|
|
109
111
|
this.attachSettings = new Map();
|
|
110
112
|
this.childSteps = [];
|
|
111
113
|
this.childRuns = new Map();
|
|
114
|
+
this.named = new Map();
|
|
112
115
|
this.processed = new Set();
|
|
113
116
|
this.eventStart = this.client.events.length;
|
|
114
117
|
this.actionEventStart = this.eventStart;
|
|
@@ -151,12 +154,9 @@ class Execution {
|
|
|
151
154
|
async navigate(client, session, url) {
|
|
152
155
|
const start = client.events.length;
|
|
153
156
|
const out = await outcome(client.send('Page.navigate', { url }, session));
|
|
154
|
-
if (out.ok && !out.result.errorText)
|
|
157
|
+
if (out.ok && !out.result.errorText && out.result.loaderId)
|
|
155
158
|
await this.poll(
|
|
156
|
-
() =>
|
|
157
|
-
client.events
|
|
158
|
-
.slice(start)
|
|
159
|
-
.some((event) => event.method === 'Page.loadEventFired' && event.sessionId === session),
|
|
159
|
+
() => loadedAfter(client.events.slice(start), session, out.result.loaderId),
|
|
160
160
|
3000
|
|
161
161
|
);
|
|
162
162
|
return out;
|
|
@@ -427,50 +427,50 @@ class Execution {
|
|
|
427
427
|
event.params.targetInfo?.targetId !== this.pageTarget &&
|
|
428
428
|
event.params.targetInfo?.targetId !== this.canaryTarget &&
|
|
429
429
|
event.params.sessionId !== this.sessions.page
|
|
430
|
-
)
|
|
431
|
-
|
|
432
|
-
if (
|
|
433
|
-
info?.browserContextId &&
|
|
434
|
-
this.vars.context &&
|
|
435
|
-
info.browserContextId !== this.vars.context
|
|
436
|
-
)
|
|
437
|
-
continue;
|
|
438
|
-
this.sessions.child = event.params.sessionId;
|
|
439
|
-
if (info?.targetId) this.targets.push(info.targetId);
|
|
440
|
-
const child = event.params.sessionId;
|
|
441
|
-
const started = (async () => {
|
|
442
|
-
const reload = this.childSteps.findIndex((step) => step.cdp === 'Page.reload');
|
|
443
|
-
if (reload < 0) await this.execute(this.childSteps, event.params.sessionId);
|
|
444
|
-
else {
|
|
445
|
-
await this.execute(this.childSteps.slice(0, reload), event.params.sessionId);
|
|
446
|
-
const start = this.client.events.length;
|
|
447
|
-
await outcome(
|
|
448
|
-
this.client.send('Runtime.runIfWaitingForDebugger', {}, event.params.sessionId)
|
|
449
|
-
);
|
|
450
|
-
await this.poll(
|
|
451
|
-
() =>
|
|
452
|
-
this.client.events
|
|
453
|
-
.slice(start)
|
|
454
|
-
.some(
|
|
455
|
-
(e) =>
|
|
456
|
-
e.method === 'Page.loadEventFired' && e.sessionId === event.params.sessionId
|
|
457
|
-
),
|
|
458
|
-
1500
|
|
459
|
-
);
|
|
460
|
-
await this.execute(this.childSteps.slice(reload), event.params.sessionId);
|
|
461
|
-
}
|
|
462
|
-
await outcome(
|
|
463
|
-
this.client.send('Runtime.runIfWaitingForDebugger', {}, event.params.sessionId)
|
|
464
|
-
);
|
|
465
|
-
})().catch((error) => this.errors.push(String(error.message ?? error)));
|
|
466
|
-
this.childRuns.set(child, started);
|
|
467
|
-
this.pending.push(started);
|
|
468
|
-
}
|
|
430
|
+
)
|
|
431
|
+
this.adopt(event);
|
|
469
432
|
}
|
|
470
433
|
await sleep(10);
|
|
471
434
|
}
|
|
472
435
|
}
|
|
473
436
|
|
|
437
|
+
adopt(event) {
|
|
438
|
+
const info = event.params.targetInfo;
|
|
439
|
+
const child = event.params.sessionId;
|
|
440
|
+
if (info?.browserContextId && this.vars.context && info.browserContextId !== this.vars.context)
|
|
441
|
+
return;
|
|
442
|
+
if (this.childWhere && !this.childMatches(info)) {
|
|
443
|
+
if (event.params.waitingForDebugger)
|
|
444
|
+
this.pending.push(outcome(this.client.send('Runtime.runIfWaitingForDebugger', {}, child)));
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
this.sessions.child = child;
|
|
448
|
+
if (info?.targetId) this.targets.push(info.targetId);
|
|
449
|
+
const started = (async () => {
|
|
450
|
+
const reload = this.childSteps.findIndex((step) => step.cdp === 'Page.reload');
|
|
451
|
+
if (reload < 0) await this.execute(this.childSteps, child);
|
|
452
|
+
else {
|
|
453
|
+
await this.execute(this.childSteps.slice(0, reload), child);
|
|
454
|
+
const start = this.client.events.length;
|
|
455
|
+
await outcome(this.client.send('Runtime.runIfWaitingForDebugger', {}, child));
|
|
456
|
+
await this.poll(
|
|
457
|
+
() =>
|
|
458
|
+
this.client.events
|
|
459
|
+
.slice(start)
|
|
460
|
+
.some((e) => e.method === 'Page.loadEventFired' && e.sessionId === child),
|
|
461
|
+
1500
|
|
462
|
+
);
|
|
463
|
+
await this.execute(this.childSteps.slice(reload), child);
|
|
464
|
+
}
|
|
465
|
+
const resumed = this.client.events.length;
|
|
466
|
+
await outcome(this.client.send('Runtime.runIfWaitingForDebugger', {}, child));
|
|
467
|
+
if (this.childSteps.some((step) => step.cdp === 'Page.enable'))
|
|
468
|
+
await this.poll(() => this.firstLoad(child, resumed), 5000);
|
|
469
|
+
})().catch((error) => this.errors.push(String(error.message ?? error)));
|
|
470
|
+
this.childRuns.set(child, started);
|
|
471
|
+
this.pending.push(started);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
474
|
async answer(event) {
|
|
475
475
|
const p = event.params;
|
|
476
476
|
const rule = this.answers.find(
|
|
@@ -498,6 +498,24 @@ class Execution {
|
|
|
498
498
|
this.calls[`answer.${p.requestId}.${event.method}`] = out;
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
+
firstLoad(session, from) {
|
|
502
|
+
const events = this.client.events.slice(from);
|
|
503
|
+
return events.some(
|
|
504
|
+
(event, index) =>
|
|
505
|
+
event.method === 'Page.frameNavigated' &&
|
|
506
|
+
event.sessionId === session &&
|
|
507
|
+
!event.params?.frame?.parentId &&
|
|
508
|
+
events
|
|
509
|
+
.slice(index + 1)
|
|
510
|
+
.some((e) => e.method === 'Page.loadEventFired' && e.sessionId === session)
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
childMatches(info) {
|
|
515
|
+
if (this.childWhere.openerId === 'page') return info?.openerId === this.pageTarget;
|
|
516
|
+
return String(info?.url ?? '').startsWith(this.childWhere.urlPrefix);
|
|
517
|
+
}
|
|
518
|
+
|
|
501
519
|
async childrenSettled() {
|
|
502
520
|
await Promise.all([...this.childRuns.values()]);
|
|
503
521
|
}
|
|
@@ -541,6 +559,7 @@ class Execution {
|
|
|
541
559
|
async operation(step, session, phase) {
|
|
542
560
|
if (step.children) {
|
|
543
561
|
this.childSteps = step.children;
|
|
562
|
+
this.childWhere = step.where;
|
|
544
563
|
return;
|
|
545
564
|
}
|
|
546
565
|
if (step.answer) {
|
|
@@ -562,12 +581,13 @@ class Execution {
|
|
|
562
581
|
}
|
|
563
582
|
|
|
564
583
|
async settle(settle) {
|
|
584
|
+
if ((settle.dependsOn ?? []).some((name) => this.refused[name])) return;
|
|
565
585
|
if (settle.variant) await sleep(this.delay);
|
|
566
586
|
if (settle.title && !this.reader) {
|
|
567
587
|
await sleep(settle.ms ?? 1500);
|
|
568
588
|
return;
|
|
569
589
|
}
|
|
570
|
-
await this.poll(async () => {
|
|
590
|
+
const met = await this.poll(async () => {
|
|
571
591
|
if (settle.beacon)
|
|
572
592
|
return (
|
|
573
593
|
(await this.requestCheck({ beacon: settle.beacon }, this.actionRequestStart)) &&
|
|
@@ -575,10 +595,20 @@ class Execution {
|
|
|
575
595
|
(await this.requestCheck({ served: this.actionDestination }, this.actionRequestStart)))
|
|
576
596
|
);
|
|
577
597
|
if (settle.title) return this.check({ title: settle.title, session: settle.session });
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
598
|
+
const events = this.client.events.slice(this.actionEventStart);
|
|
599
|
+
return events.some(
|
|
600
|
+
(event, index) =>
|
|
601
|
+
event.method === 'Page.frameNavigated' &&
|
|
602
|
+
!event.params?.frame?.parentId &&
|
|
603
|
+
events
|
|
604
|
+
.slice(index + 1)
|
|
605
|
+
.some((e) => e.method === 'Page.loadEventFired' && e.sessionId === event.sessionId)
|
|
606
|
+
);
|
|
581
607
|
}, settle.ms ?? 1500);
|
|
608
|
+
if (settle.required && !met)
|
|
609
|
+
this.errors.push(
|
|
610
|
+
`settle: no ${settle.beacon ? `beacon from ${settle.beacon}` : settle.title ? `title ${settle.title}` : 'load'} within ${settle.ms ?? 1500}ms`
|
|
611
|
+
);
|
|
582
612
|
}
|
|
583
613
|
|
|
584
614
|
async guess(guess, session) {
|
|
@@ -607,18 +637,19 @@ class Execution {
|
|
|
607
637
|
if (step.cdp === 'Target.setAutoAttach' && step.params?.autoAttach === false)
|
|
608
638
|
this.cleanupEventStart = this.client.events.length;
|
|
609
639
|
const params = { ...step.params };
|
|
610
|
-
if (
|
|
611
|
-
/^Storage\.(getCookies|setCookies|clearCookies)$/.test(step.cdp) &&
|
|
612
|
-
this.vars.context &&
|
|
613
|
-
!params.browserContextId
|
|
614
|
-
)
|
|
615
|
-
params.browserContextId = this.vars.context;
|
|
616
640
|
const session =
|
|
617
641
|
step.session === 'page-or-browser' &&
|
|
618
642
|
supported(['browserSession'], this.options.capabilities) &&
|
|
619
643
|
/^Storage\.(getCookies|setCookies|clearCookies)$/.test(step.cdp)
|
|
620
644
|
? undefined
|
|
621
645
|
: sessionId;
|
|
646
|
+
if (
|
|
647
|
+
/^Storage\.(getCookies|setCookies|clearCookies)$/.test(step.cdp) &&
|
|
648
|
+
this.vars.context &&
|
|
649
|
+
!params.browserContextId &&
|
|
650
|
+
session === undefined
|
|
651
|
+
)
|
|
652
|
+
params.browserContextId = this.vars.context;
|
|
622
653
|
const out = await outcome(this.client.send(step.cdp, params, session));
|
|
623
654
|
if (step.cdp === 'Target.setAutoAttach' && out.ok) {
|
|
624
655
|
if (params.autoAttach) this.attachSettings.set(session, true);
|
|
@@ -673,11 +704,11 @@ class Execution {
|
|
|
673
704
|
if (pre) expression = `(() => { ${pre} return (${expression}); })()`;
|
|
674
705
|
const promise = this.evaluate(expression, session);
|
|
675
706
|
if (step.background) {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
);
|
|
707
|
+
const done = promise.then((out) => {
|
|
708
|
+
if (typeof step.as === 'string') this.calls[step.as] = out;
|
|
709
|
+
});
|
|
710
|
+
this.pending.push(done);
|
|
711
|
+
if (typeof step.as === 'string') this.named.set(step.as, done);
|
|
681
712
|
return { ok: true, result: { pending: true } };
|
|
682
713
|
}
|
|
683
714
|
const out = await promise;
|
|
@@ -689,9 +720,38 @@ class Execution {
|
|
|
689
720
|
return out;
|
|
690
721
|
}
|
|
691
722
|
|
|
723
|
+
async namedSettled(names) {
|
|
724
|
+
const waits = names.map((name) => this.named.get(name)).filter(Boolean);
|
|
725
|
+
if (!waits.length) return true;
|
|
726
|
+
let timer;
|
|
727
|
+
const expired = new Promise((resolve) => {
|
|
728
|
+
timer = setTimeout(() => resolve(false), this.options.namedResultMs ?? namedResultMs);
|
|
729
|
+
});
|
|
730
|
+
const done = await Promise.race([Promise.all(waits).then(() => true), expired]);
|
|
731
|
+
clearTimeout(timer);
|
|
732
|
+
if (!done) this.pending = this.pending.filter((work) => !waits.includes(work));
|
|
733
|
+
return done;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
async ready(c) {
|
|
737
|
+
if (c.observer === 'unguarded' && !this.reader) return false;
|
|
738
|
+
return this.namedReady(c);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async namedReady(c) {
|
|
742
|
+
const names = [c.result, ...(c.compare ?? []), c.refused, c.reached]
|
|
743
|
+
.flat()
|
|
744
|
+
.filter((name) => typeof name === 'string' && this.named.has(name));
|
|
745
|
+
if (!names.length || (await this.namedSettled(names))) return true;
|
|
746
|
+
this.errors.push(
|
|
747
|
+
`${names.join(', ')} did not complete within ${this.options.namedResultMs ?? namedResultMs}ms`
|
|
748
|
+
);
|
|
749
|
+
return false;
|
|
750
|
+
}
|
|
751
|
+
|
|
692
752
|
async check(check) {
|
|
693
753
|
const c = expand(check, this.vars);
|
|
694
|
-
if (
|
|
754
|
+
if (!(await this.ready(c))) return false;
|
|
695
755
|
const events = this.client.events.slice(this.eventStart);
|
|
696
756
|
const evidence = await this.evidence(c);
|
|
697
757
|
if (evidence !== undefined) return evidence;
|
|
@@ -945,16 +1005,87 @@ const inactive = (vector, target, capabilities) =>
|
|
|
945
1005
|
!supported(vector.requires, capabilities) ||
|
|
946
1006
|
(vector.scope !== 'any' && vector.scope !== capabilities.scope);
|
|
947
1007
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1008
|
+
const allSteps = (list) => list.flatMap((step) => [step, ...allSteps(step.children ?? [])]);
|
|
1009
|
+
const browserContextStep = (vector, capabilities) =>
|
|
1010
|
+
allSteps([...vector.setup, ...vector.trigger]).some(
|
|
1011
|
+
(step) =>
|
|
1012
|
+
step.params?.browserContextId !== undefined ||
|
|
1013
|
+
(step.session === 'page-or-browser' &&
|
|
1014
|
+
/^Storage\.(getCookies|setCookies|clearCookies)$/.test(step.cdp ?? '') &&
|
|
1015
|
+
supported(['browserContexts'], capabilities))
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
const originDefaults = { plain: 'ordinary', cross: 'client', log: 'ordinary', own: 'client' };
|
|
1019
|
+
const knownOrigins = [
|
|
1020
|
+
'ordinary',
|
|
1021
|
+
'ordinary-same-site',
|
|
1022
|
+
'seven',
|
|
1023
|
+
'vault',
|
|
1024
|
+
'vault-x',
|
|
1025
|
+
'apex',
|
|
1026
|
+
'own',
|
|
1027
|
+
'client',
|
|
1028
|
+
'plain',
|
|
1029
|
+
'cross',
|
|
1030
|
+
'log',
|
|
1031
|
+
];
|
|
1032
|
+
|
|
1033
|
+
function nullOrigins(value, targets) {
|
|
1034
|
+
return [...q(value).matchAll(/\{([A-Za-z][A-Za-z-]*)\}/g)]
|
|
1035
|
+
.map((match) => (match[1] === 'beacon' || match[1] === 'probe' ? 'log' : match[1]))
|
|
1036
|
+
.map((name) => (targets[name] === undefined ? (originDefaults[name] ?? name) : name))
|
|
1037
|
+
.filter((name) => knownOrigins.includes(name) && (targets[name] ?? null) === null);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function prune(check, targets) {
|
|
1041
|
+
if (!check || typeof check !== 'object') return { check };
|
|
1042
|
+
if (check.any) {
|
|
1043
|
+
const parts = check.any.map((item) => prune(item, targets));
|
|
1044
|
+
const kept = parts.filter((part) => !part.missing).map((part) => part.check);
|
|
1045
|
+
return kept.length ? { check: { ...check, any: kept } } : { missing: parts[0]?.missing };
|
|
1046
|
+
}
|
|
1047
|
+
if (check.all || check.not) {
|
|
1048
|
+
const parts = (check.all ?? [check.not]).map((item) => prune(item, targets));
|
|
1049
|
+
const missing = parts.find((part) => part.missing)?.missing;
|
|
1050
|
+
if (missing) return { missing };
|
|
1051
|
+
return {
|
|
1052
|
+
check: check.all
|
|
1053
|
+
? { ...check, all: parts.map((part) => part.check) }
|
|
1054
|
+
: { ...check, not: parts[0].check },
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
const missing = nullOrigins(check, targets)[0];
|
|
1058
|
+
return missing ? { missing } : { check };
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function available(vector, target, targets = {}) {
|
|
1062
|
+
const fixed = nullOrigins(
|
|
1063
|
+
[target, '{plain}', vector.control.origin, vector.setup, vector.trigger].map((value) =>
|
|
1064
|
+
typeof value === 'string' && !value.startsWith('{') ? `{${value}}` : value
|
|
1065
|
+
),
|
|
1066
|
+
targets
|
|
1067
|
+
)[0];
|
|
1068
|
+
if (fixed) return { missing: fixed };
|
|
1069
|
+
const live = prune(vector.liveCheck, targets);
|
|
1070
|
+
if (live.missing) return { missing: live.missing };
|
|
1071
|
+
const observe = {};
|
|
1072
|
+
for (const [name, observation] of Object.entries(vector.observe ?? {})) {
|
|
1073
|
+
const pruned = prune(observation.check, targets);
|
|
1074
|
+
if (pruned.missing) return { missing: pruned.missing };
|
|
1075
|
+
const either = observation.eitherWhen ? prune(observation.eitherWhen, targets) : undefined;
|
|
1076
|
+
observe[name] = {
|
|
1077
|
+
...observation,
|
|
1078
|
+
check: pruned.check,
|
|
1079
|
+
...(observation.eitherWhen ? { eitherWhen: either.missing ? undefined : either.check } : {}),
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
return {
|
|
1083
|
+
vector: {
|
|
1084
|
+
...vector,
|
|
1085
|
+
...(vector.liveCheck ? { liveCheck: live.check } : {}),
|
|
1086
|
+
...(vector.observe ? { observe } : {}),
|
|
1087
|
+
},
|
|
1088
|
+
};
|
|
958
1089
|
}
|
|
959
1090
|
|
|
960
1091
|
function variables(targets, target, side, slot, index) {
|
|
@@ -976,7 +1107,9 @@ function variables(targets, target, side, slot, index) {
|
|
|
976
1107
|
}
|
|
977
1108
|
|
|
978
1109
|
async function executeRow(job, opts, clients, set, slot, index) {
|
|
979
|
-
const {
|
|
1110
|
+
const { target, variant } = job;
|
|
1111
|
+
const scoped = available(job.vector, target, opts.targets);
|
|
1112
|
+
const vector = scoped.vector ?? job.vector;
|
|
980
1113
|
const base = {
|
|
981
1114
|
id: vector.id,
|
|
982
1115
|
target,
|
|
@@ -985,7 +1118,7 @@ async function executeRow(job, opts, clients, set, slot, index) {
|
|
|
985
1118
|
via: vector.control.via,
|
|
986
1119
|
transportVariant: clients.transportVariant === true,
|
|
987
1120
|
};
|
|
988
|
-
const unavailable =
|
|
1121
|
+
const unavailable = scoped.missing;
|
|
989
1122
|
if (unavailable)
|
|
990
1123
|
return {
|
|
991
1124
|
...base,
|
|
@@ -1002,8 +1135,27 @@ async function executeRow(job, opts, clients, set, slot, index) {
|
|
|
1002
1135
|
live: 'not-run',
|
|
1003
1136
|
appliesTo: vector.appliesTo ?? [],
|
|
1004
1137
|
};
|
|
1005
|
-
if (inactive(vector, target, opts.capabilities))
|
|
1006
|
-
|
|
1138
|
+
if (inactive(vector, target, opts.capabilities)) {
|
|
1139
|
+
const missing = (vector.requires ?? []).find((cap) => !supported([cap], opts.capabilities));
|
|
1140
|
+
return {
|
|
1141
|
+
...base,
|
|
1142
|
+
control: 'skip',
|
|
1143
|
+
guarded: 'skip',
|
|
1144
|
+
live: 'skip',
|
|
1145
|
+
reason: missing ? `not-supported:${missing}` : 'not-applicable',
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
if (
|
|
1149
|
+
!supported(['browserSession'], opts.capabilities) &&
|
|
1150
|
+
browserContextStep(vector, opts.capabilities)
|
|
1151
|
+
)
|
|
1152
|
+
return {
|
|
1153
|
+
...base,
|
|
1154
|
+
control: 'skip',
|
|
1155
|
+
guarded: 'skip',
|
|
1156
|
+
live: 'skip',
|
|
1157
|
+
reason: 'not-supported:browserSession',
|
|
1158
|
+
};
|
|
1007
1159
|
if (needsObserver(vector) && !clients.unguarded)
|
|
1008
1160
|
return {
|
|
1009
1161
|
...base,
|
|
@@ -1172,10 +1324,22 @@ export function summary({ set, results, guard = 'reference', sha = 'unknown' })
|
|
|
1172
1324
|
const observerSkipped = results.filter(
|
|
1173
1325
|
(row) => row.reason === 'not-supported:unguarded-observer'
|
|
1174
1326
|
).length;
|
|
1327
|
+
const skips = {};
|
|
1328
|
+
for (const row of results)
|
|
1329
|
+
if (row.control === 'skip')
|
|
1330
|
+
skips[row.reason ?? 'unknown'] = (skips[row.reason ?? 'unknown'] ?? 0) + 1;
|
|
1331
|
+
const skipped = Object.values(skips).reduce((sum, count) => sum + count, 0);
|
|
1332
|
+
const line = observerSkipped ? value.line.replace('GUARD-PASS', 'GUARD-FAIL') : value.line;
|
|
1175
1333
|
return {
|
|
1176
1334
|
...value,
|
|
1177
1335
|
pass: value.pass && observerSkipped === 0,
|
|
1178
|
-
line:
|
|
1336
|
+
line: skipped
|
|
1337
|
+
? `${line} skips ${skipped} (${Object.entries(skips)
|
|
1338
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
1339
|
+
.map(([reason, count]) => `${reason}=${count}`)
|
|
1340
|
+
.join(', ')})`
|
|
1341
|
+
: line,
|
|
1342
|
+
skips,
|
|
1179
1343
|
observerSkipped,
|
|
1180
1344
|
counts: Object.fromEntries(
|
|
1181
1345
|
['control', 'guarded', 'live'].map((side) => [
|
package/guard-vectors.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"setVersion": "1.7.
|
|
3
|
+
"setVersion": "1.7.5",
|
|
4
4
|
"sources": [
|
|
5
5
|
{
|
|
6
6
|
"repo": "ai-ecoverse/slicc-cli",
|
|
@@ -116,6 +116,14 @@
|
|
|
116
116
|
{
|
|
117
117
|
"page": "navigate",
|
|
118
118
|
"url": "{ordinary}/doc?n=next-{tag}"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"settle": {
|
|
122
|
+
"beacon": "{ordinary}",
|
|
123
|
+
"ms": 5000,
|
|
124
|
+
"required": true,
|
|
125
|
+
"dependsOn": ["refused.3"]
|
|
126
|
+
}
|
|
119
127
|
}
|
|
120
128
|
],
|
|
121
129
|
"trigger": [
|
|
@@ -318,6 +326,14 @@
|
|
|
318
326
|
{
|
|
319
327
|
"page": "navigate",
|
|
320
328
|
"url": "{ordinary}/doc?n=next-{tag}"
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"settle": {
|
|
332
|
+
"beacon": "{ordinary}",
|
|
333
|
+
"ms": 5000,
|
|
334
|
+
"required": true,
|
|
335
|
+
"dependsOn": ["refused.3"]
|
|
336
|
+
}
|
|
321
337
|
}
|
|
322
338
|
],
|
|
323
339
|
"trigger": [
|
|
@@ -929,6 +945,14 @@
|
|
|
929
945
|
{
|
|
930
946
|
"page": "navigate",
|
|
931
947
|
"url": "{ordinary}/doc?n=next-{tag}"
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
"settle": {
|
|
951
|
+
"beacon": "{ordinary}",
|
|
952
|
+
"ms": 5000,
|
|
953
|
+
"required": true,
|
|
954
|
+
"dependsOn": ["refused.3"]
|
|
955
|
+
}
|
|
932
956
|
}
|
|
933
957
|
],
|
|
934
958
|
"trigger": [
|
|
@@ -1540,6 +1564,14 @@
|
|
|
1540
1564
|
{
|
|
1541
1565
|
"page": "navigate",
|
|
1542
1566
|
"url": "{ordinary}/doc?n=next-{tag}"
|
|
1567
|
+
},
|
|
1568
|
+
{
|
|
1569
|
+
"settle": {
|
|
1570
|
+
"beacon": "{ordinary}",
|
|
1571
|
+
"ms": 5000,
|
|
1572
|
+
"required": true,
|
|
1573
|
+
"dependsOn": ["refused.3"]
|
|
1574
|
+
}
|
|
1543
1575
|
}
|
|
1544
1576
|
],
|
|
1545
1577
|
"trigger": [
|
|
@@ -2151,6 +2183,14 @@
|
|
|
2151
2183
|
{
|
|
2152
2184
|
"page": "navigate",
|
|
2153
2185
|
"url": "{ordinary}/doc?n=next-{tag}"
|
|
2186
|
+
},
|
|
2187
|
+
{
|
|
2188
|
+
"settle": {
|
|
2189
|
+
"beacon": "{ordinary}",
|
|
2190
|
+
"ms": 5000,
|
|
2191
|
+
"required": true,
|
|
2192
|
+
"dependsOn": ["refused.3"]
|
|
2193
|
+
}
|
|
2154
2194
|
}
|
|
2155
2195
|
],
|
|
2156
2196
|
"trigger": [
|
|
@@ -2590,6 +2630,14 @@
|
|
|
2590
2630
|
{
|
|
2591
2631
|
"page": "navigate",
|
|
2592
2632
|
"url": "{ordinary}/doc?n=next-{tag}"
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
"settle": {
|
|
2636
|
+
"beacon": "{ordinary}",
|
|
2637
|
+
"ms": 5000,
|
|
2638
|
+
"required": true,
|
|
2639
|
+
"dependsOn": ["refused.3"]
|
|
2640
|
+
}
|
|
2593
2641
|
}
|
|
2594
2642
|
],
|
|
2595
2643
|
"trigger": [
|
|
@@ -3029,6 +3077,14 @@
|
|
|
3029
3077
|
{
|
|
3030
3078
|
"page": "navigate",
|
|
3031
3079
|
"url": "{ordinary}/doc?n=next-{tag}"
|
|
3080
|
+
},
|
|
3081
|
+
{
|
|
3082
|
+
"settle": {
|
|
3083
|
+
"beacon": "{ordinary}",
|
|
3084
|
+
"ms": 5000,
|
|
3085
|
+
"required": true,
|
|
3086
|
+
"dependsOn": ["refused.3"]
|
|
3087
|
+
}
|
|
3032
3088
|
}
|
|
3033
3089
|
],
|
|
3034
3090
|
"trigger": [
|
|
@@ -3472,6 +3528,14 @@
|
|
|
3472
3528
|
{
|
|
3473
3529
|
"page": "navigate",
|
|
3474
3530
|
"url": "{ordinary}/doc?n=next-{tag}"
|
|
3531
|
+
},
|
|
3532
|
+
{
|
|
3533
|
+
"settle": {
|
|
3534
|
+
"beacon": "{ordinary}",
|
|
3535
|
+
"ms": 5000,
|
|
3536
|
+
"required": true,
|
|
3537
|
+
"dependsOn": ["refused.3"]
|
|
3538
|
+
}
|
|
3475
3539
|
}
|
|
3476
3540
|
],
|
|
3477
3541
|
"trigger": [
|
|
@@ -3929,6 +3993,9 @@
|
|
|
3929
3993
|
}
|
|
3930
3994
|
}
|
|
3931
3995
|
],
|
|
3996
|
+
"where": {
|
|
3997
|
+
"openerId": "page"
|
|
3998
|
+
},
|
|
3932
3999
|
"requires": ["autoAttach"]
|
|
3933
4000
|
},
|
|
3934
4001
|
{
|
|
@@ -3940,6 +4007,16 @@
|
|
|
3940
4007
|
"url": "{ordinary}/doc?n=popup-{tag}&again=1",
|
|
3941
4008
|
"session": "child",
|
|
3942
4009
|
"requires": ["autoAttach"]
|
|
4010
|
+
},
|
|
4011
|
+
{
|
|
4012
|
+
"settle": {
|
|
4013
|
+
"beacon": "{ordinary}",
|
|
4014
|
+
"session": "child",
|
|
4015
|
+
"ms": 5000,
|
|
4016
|
+
"required": true,
|
|
4017
|
+
"dependsOn": ["refused.2", "refused.5"]
|
|
4018
|
+
},
|
|
4019
|
+
"requires": ["autoAttach"]
|
|
3943
4020
|
}
|
|
3944
4021
|
],
|
|
3945
4022
|
"trigger": [
|
|
@@ -4180,6 +4257,9 @@
|
|
|
4180
4257
|
"cdp": "Page.reload"
|
|
4181
4258
|
}
|
|
4182
4259
|
],
|
|
4260
|
+
"where": {
|
|
4261
|
+
"openerId": "page"
|
|
4262
|
+
},
|
|
4183
4263
|
"requires": ["autoAttach"]
|
|
4184
4264
|
}
|
|
4185
4265
|
],
|
|
@@ -4415,6 +4495,9 @@
|
|
|
4415
4495
|
"cdp": "Page.reload"
|
|
4416
4496
|
}
|
|
4417
4497
|
],
|
|
4498
|
+
"where": {
|
|
4499
|
+
"openerId": "page"
|
|
4500
|
+
},
|
|
4418
4501
|
"requires": ["autoAttach"]
|
|
4419
4502
|
}
|
|
4420
4503
|
],
|
|
@@ -4847,7 +4930,7 @@
|
|
|
4847
4930
|
},
|
|
4848
4931
|
"beacon": {
|
|
4849
4932
|
"means": "a beacon from {target}",
|
|
4850
|
-
"dependsOn": ["refused.7", "refused.8"],
|
|
4933
|
+
"dependsOn": ["refused.3", "refused.7", "refused.8"],
|
|
4851
4934
|
"requires": ["autoAttach"],
|
|
4852
4935
|
"check": {
|
|
4853
4936
|
"beacon": "{target}",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-browsers",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.5",
|
|
4
4
|
"description": "SLICC's shared browser catalog, JSON schemas and discovery conformance corpus",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -64,12 +64,14 @@
|
|
|
64
64
|
"trust": "npx fledgling sync -y --skip-publish",
|
|
65
65
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish",
|
|
66
66
|
"release": "semantic-release",
|
|
67
|
-
"test:guard": "
|
|
67
|
+
"test:guard": "npm run test:guard-integration && npm run test:guard-sweep",
|
|
68
|
+
"test:guard-integration": "node --test 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",
|
|
69
|
+
"test:guard-sweep": "node scripts/test-guards.mjs",
|
|
68
70
|
"test:guard-target": "node --test test/integration/guard-runner.test.mjs",
|
|
69
71
|
"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"
|
|
70
72
|
},
|
|
71
73
|
"devDependencies": {
|
|
72
|
-
"@ai-ecoverse/slicc-shared-web": "1.
|
|
74
|
+
"@ai-ecoverse/slicc-shared-web": "1.21.1",
|
|
73
75
|
"@biomejs/biome": "2.5.15",
|
|
74
76
|
"ajv": "8.20.0",
|
|
75
77
|
"ajv-formats": "3.0.1",
|
package/protected.json
CHANGED
|
@@ -545,6 +545,22 @@
|
|
|
545
545
|
"children": {
|
|
546
546
|
"$ref": "#/$defs/steps"
|
|
547
547
|
},
|
|
548
|
+
"where": {
|
|
549
|
+
"oneOf": [
|
|
550
|
+
{
|
|
551
|
+
"type": "object",
|
|
552
|
+
"additionalProperties": false,
|
|
553
|
+
"required": ["openerId"],
|
|
554
|
+
"properties": { "openerId": { "const": "page" } }
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
"type": "object",
|
|
558
|
+
"additionalProperties": false,
|
|
559
|
+
"required": ["urlPrefix"],
|
|
560
|
+
"properties": { "urlPrefix": { "type": "string", "minLength": 1 } }
|
|
561
|
+
}
|
|
562
|
+
]
|
|
563
|
+
},
|
|
548
564
|
"on": {
|
|
549
565
|
"$ref": "#/$defs/on"
|
|
550
566
|
},
|
|
@@ -578,9 +594,23 @@
|
|
|
578
594
|
},
|
|
579
595
|
"session": {
|
|
580
596
|
"$ref": "#/$defs/session"
|
|
597
|
+
},
|
|
598
|
+
"required": {
|
|
599
|
+
"const": true
|
|
600
|
+
},
|
|
601
|
+
"dependsOn": {
|
|
602
|
+
"type": "array",
|
|
603
|
+
"uniqueItems": true,
|
|
604
|
+
"items": {
|
|
605
|
+
"type": "string",
|
|
606
|
+
"pattern": "^(refused\\.[0-9]+|[A-Za-z][A-Za-z0-9]*)$"
|
|
607
|
+
}
|
|
581
608
|
}
|
|
582
609
|
}
|
|
583
610
|
},
|
|
611
|
+
"requires": {
|
|
612
|
+
"$ref": "#/$defs/requires"
|
|
613
|
+
},
|
|
584
614
|
"on": {
|
|
585
615
|
"$ref": "#/$defs/on"
|
|
586
616
|
}
|