@reticlehq/server 2.10.0 → 2.12.0
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/SKILL.md +73 -11
- package/dist/bridge/bridge.d.ts +0 -13
- package/dist/bridge/bridge.js +1 -1
- package/dist/bridge/bridge.test-harness.d.ts +2 -0
- package/dist/bridge/bridge.test-harness.js +8 -1
- package/dist/capsule/capsule-store.d.ts +2 -1
- package/dist/capsule/capsule-store.js +1 -1
- package/dist/capsule/causal-summary.d.ts +2 -2
- package/dist/chromium-launch-options.d.ts +28 -0
- package/dist/chromium-launch-options.js +29 -0
- package/dist/cli/cli-doctor.js +8 -0
- package/dist/cli/cli-flow-commands.d.ts +3 -1
- package/dist/cli/cli-flow-commands.js +34 -4
- package/dist/cli/cli-kill.d.ts +2 -1
- package/dist/cli/cli-parse.d.ts +3 -1
- package/dist/cli/cli-parse.js +29 -7
- package/dist/cli/cli-update-commands.js +20 -1
- package/dist/cli/cli-verify.d.ts +1 -1
- package/dist/cli/cli-verify.js +2 -2
- package/dist/cli/cloud-cli.js +12 -6
- package/dist/cli/config-discovery.d.ts +2 -1
- package/dist/cli/doctor-rows.d.ts +3 -1
- package/dist/cli/doctor-rows.js +3 -1
- package/dist/cli/gate-exit.d.ts +21 -0
- package/dist/cli/gate-exit.js +21 -0
- package/dist/cli/gate-hook-message.d.ts +33 -0
- package/dist/cli/gate-hook-message.js +46 -0
- package/dist/cli/mcp-command.d.ts +25 -0
- package/dist/cli/mcp-command.js +143 -0
- package/dist/cli/sibling-ports.d.ts +37 -0
- package/dist/cli/sibling-ports.js +56 -0
- package/dist/cli.js +37 -110
- package/dist/cloud/cloud-config.d.ts +2 -1
- package/dist/cloud/cloud-sync.d.ts +11 -0
- package/dist/cloud/cloud-sync.js +32 -0
- package/dist/crawl/crawl.d.ts +9 -13
- package/dist/crawl/crawl.js +20 -3
- package/dist/daemon/daemon-resolve.d.ts +79 -0
- package/dist/daemon/daemon-resolve.js +118 -0
- package/dist/daemon/free-port.d.ts +29 -0
- package/dist/daemon/free-port.js +55 -0
- package/dist/daemon/heartbeat.d.ts +0 -4
- package/dist/daemon/heartbeat.js +2 -2
- package/dist/dev/stale-issue-guard.d.ts +41 -0
- package/dist/dev/stale-issue-guard.js +75 -0
- package/dist/domain/domain-tools.js +3 -2
- package/dist/events/contradictions.d.ts +41 -0
- package/dist/events/contradictions.js +125 -12
- package/dist/events/declared.d.ts +14 -0
- package/dist/events/declared.js +33 -0
- package/dist/events/echo-mismatch.js +14 -0
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +17 -0
- package/dist/events/predicate-asks.d.ts +30 -0
- package/dist/events/predicate-asks.js +48 -0
- package/dist/events/predicate-eval.js +254 -25
- package/dist/events/predicate-parse.d.ts +0 -6
- package/dist/events/predicate-parse.js +51 -5
- package/dist/events/predicate-schema.d.ts +62 -0
- package/dist/events/predicate-schema.js +162 -4
- package/dist/events/predicate.d.ts +15 -0
- package/dist/events/predicate.js +41 -2
- package/dist/flows/coverage.d.ts +0 -8
- package/dist/flows/coverage.js +1 -1
- package/dist/flows/decision.d.ts +5 -1
- package/dist/flows/decision.js +33 -7
- package/dist/flows/flake.d.ts +0 -2
- package/dist/flows/flake.js +1 -1
- package/dist/flows/flow-intent.d.ts +52 -0
- package/dist/flows/flow-intent.js +123 -0
- package/dist/flows/flow-replay-run.js +27 -4
- package/dist/flows/flow-success.js +15 -3
- package/dist/flows/flow-tools.js +65 -8
- package/dist/flows/flows.d.ts +6 -1
- package/dist/flows/flows.js +34 -22
- package/dist/flows/predicate-to-expect.js +14 -3
- package/dist/flows/server-verify.js +16 -2
- package/dist/flows/verify-change-tools.js +9 -2
- package/dist/honesty/blind-spots.d.ts +8 -16
- package/dist/honesty/blind-spots.js +18 -5
- package/dist/honesty/feature-capture.d.ts +150 -0
- package/dist/honesty/feature-capture.js +233 -0
- package/dist/honesty/gap-ledger.d.ts +52 -0
- package/dist/honesty/gap-ledger.js +63 -0
- package/dist/honesty/instrumentation-gaps.d.ts +79 -0
- package/dist/honesty/instrumentation-gaps.js +77 -0
- package/dist/honesty/observability.d.ts +37 -0
- package/dist/honesty/observability.js +40 -0
- package/dist/honesty/tool-hit-rate.d.ts +86 -0
- package/dist/honesty/tool-hit-rate.js +90 -0
- package/dist/honesty/undeclared-change.d.ts +51 -0
- package/dist/honesty/undeclared-change.js +56 -0
- package/dist/honesty/verified.d.ts +15 -0
- package/dist/honesty/verified.js +68 -4
- package/dist/http-server.d.ts +0 -1
- package/dist/http-server.js +1 -1
- package/dist/hunt/hunt-report.d.ts +0 -2
- package/dist/hunt/hunt-report.js +1 -1
- package/dist/impact/impact-store.d.ts +0 -5
- package/dist/impact/impact-store.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +41 -0
- package/dist/init/agent-rules.d.ts +17 -0
- package/dist/init/agent-rules.js +31 -1
- package/dist/init/capabilities.d.ts +0 -2
- package/dist/init/capabilities.js +1 -1
- package/dist/init/closing-hint.d.ts +36 -0
- package/dist/init/closing-hint.js +120 -0
- package/dist/init/confirm.d.ts +0 -25
- package/dist/init/confirm.js +3 -3
- package/dist/init/cra.d.ts +14 -1
- package/dist/init/cra.js +43 -12
- package/dist/init/dev-script.d.ts +48 -0
- package/dist/init/dev-script.js +63 -0
- package/dist/init/mcp.d.ts +16 -5
- package/dist/init/mcp.js +26 -10
- package/dist/init/node-io.js +3 -0
- package/dist/init/plan-framework.d.ts +0 -5
- package/dist/init/plan-framework.js +83 -9
- package/dist/init/plan.d.ts +10 -30
- package/dist/init/plan.js +30 -15
- package/dist/init/refresh-rules.d.ts +39 -0
- package/dist/init/refresh-rules.js +51 -0
- package/dist/init/run.d.ts +2 -10
- package/dist/init/run.js +26 -68
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +33 -10
- package/dist/init/snippets.js +53 -10
- package/dist/input/real-input.d.ts +2 -0
- package/dist/input/real-input.js +3 -2
- package/dist/input/scroll-find.d.ts +7 -4
- package/dist/input/scroll-find.js +63 -25
- package/dist/intent/inline-intent.d.ts +46 -0
- package/dist/intent/inline-intent.js +103 -0
- package/dist/intent/intent-store.d.ts +37 -0
- package/dist/intent/intent-store.js +112 -0
- package/dist/intent/intent-tools.d.ts +2 -0
- package/dist/intent/intent-tools.js +80 -0
- package/dist/intent/open-intents.d.ts +21 -0
- package/dist/intent/open-intents.js +25 -0
- package/dist/journal/envelope.d.ts +2 -2
- package/dist/journal/journal-recorder.d.ts +23 -0
- package/dist/journal/journal-recorder.js +35 -0
- package/dist/license/license.d.ts +8 -2
- package/dist/license/license.js +10 -15
- package/dist/mcp/attach-memory.d.ts +67 -0
- package/dist/mcp/attach-memory.js +142 -0
- package/dist/mcp/mcp-proxy.d.ts +0 -7
- package/dist/mcp/mcp-proxy.js +14 -2
- package/dist/mcp/mcp.d.ts +2 -0
- package/dist/mcp/mcp.js +45 -23
- package/dist/mcp/server-instructions.js +45 -3
- package/dist/mcp/tool-catalog-cache.d.ts +8 -0
- package/dist/mcp/tool-catalog-cache.js +10 -0
- package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
- package/dist/oracles/flow-instrument-gaps.js +4 -5
- package/dist/oracles/self-instrument.d.ts +9 -9
- package/dist/oracles/self-instrument.js +19 -13
- package/dist/pool/browser-pool.d.ts +12 -1
- package/dist/pool/browser-pool.js +31 -1
- package/dist/pool/playwright-launcher.js +3 -1
- package/dist/project/artifact-root.d.ts +83 -0
- package/dist/project/artifact-root.js +112 -0
- package/dist/project/fs-port.d.ts +3 -0
- package/dist/project/fs-port.js +3 -2
- package/dist/project/memory-fs.d.ts +9 -0
- package/dist/project/memory-fs.js +93 -0
- package/dist/project/project-store.d.ts +17 -0
- package/dist/project/project-store.js +33 -0
- package/dist/project/project-tools.js +3 -32
- package/dist/project/remember-project.d.ts +24 -0
- package/dist/project/remember-project.js +40 -0
- package/dist/project/reticle-dir.d.ts +2 -0
- package/dist/project/reticle-dir.js +1 -0
- package/dist/project/session-root.d.ts +15 -0
- package/dist/project/session-root.js +31 -0
- package/dist/runs/context-tools.d.ts +2 -0
- package/dist/runs/context-tools.js +89 -0
- package/dist/runs/run-context.d.ts +40 -0
- package/dist/runs/run-context.js +158 -0
- package/dist/runs/run-diff.d.ts +2 -1
- package/dist/runs/run-tools.js +2 -1
- package/dist/runs/verification-sync.js +2 -2
- package/dist/session/dev-server-probe.d.ts +0 -8
- package/dist/session/dev-server-probe.js +1 -1
- package/dist/session/last-act.d.ts +7 -1
- package/dist/session/last-act.js +2 -2
- package/dist/session/lease-availability.d.ts +40 -0
- package/dist/session/lease-availability.js +43 -0
- package/dist/session/live-control-tools.js +1 -42
- package/dist/session/no-session-diagnosis.d.ts +20 -0
- package/dist/session/no-session-diagnosis.js +41 -12
- package/dist/session/no-session-next-action.d.ts +5 -0
- package/dist/session/no-session-next-action.js +35 -3
- package/dist/session/no-session-watch.d.ts +8 -0
- package/dist/session/no-session-watch.js +83 -65
- package/dist/session/output-budget.js +4 -1
- package/dist/session/session-health.d.ts +9 -0
- package/dist/session/session-health.js +23 -0
- package/dist/session/session-manager.js +50 -1
- package/dist/session/session-successor.d.ts +37 -0
- package/dist/session/session-successor.js +78 -0
- package/dist/session/session.d.ts +40 -1
- package/dist/session/session.js +84 -2
- package/dist/session/stall-clock.d.ts +18 -0
- package/dist/session/stall-clock.js +48 -0
- package/dist/telemetry/automation-hint.d.ts +2 -1
- package/dist/telemetry/daemon-telemetry.js +12 -23
- package/dist/telemetry/feedback-outbox.d.ts +2 -1
- package/dist/telemetry/project-profile.d.ts +0 -8
- package/dist/telemetry/project-profile.js +1 -1
- package/dist/telemetry/run-telemetry.d.ts +0 -2
- package/dist/telemetry/run-telemetry.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +1 -1
- package/dist/telemetry/telemetry.d.ts +12 -3
- package/dist/telemetry/telemetry.js +25 -5
- package/dist/telemetry/verification-of.js +2 -2
- package/dist/tools/act-tools.js +159 -24
- package/dist/tools/assert-source.d.ts +22 -0
- package/dist/tools/assert-source.js +53 -0
- package/dist/tools/assert-verdict.d.ts +21 -2
- package/dist/tools/assert-verdict.js +59 -4
- package/dist/tools/browser-tools.js +0 -20
- package/dist/tools/complete-snapshot.d.ts +48 -0
- package/dist/tools/complete-snapshot.js +75 -0
- package/dist/tools/contract-tools.js +5 -3
- package/dist/tools/coverage-tools.d.ts +10 -1
- package/dist/tools/coverage-tools.js +124 -37
- package/dist/tools/dynamic-tools.js +29 -8
- package/dist/tools/error-recovery.d.ts +24 -14
- package/dist/tools/error-recovery.js +70 -9
- package/dist/tools/feedback-tools.d.ts +1 -1
- package/dist/tools/feedback-tools.js +2 -2
- package/dist/tools/invoke-tool.js +62 -2
- package/dist/tools/lease-hint.d.ts +10 -0
- package/dist/tools/lease-hint.js +53 -4
- package/dist/tools/lease-tools.d.ts +5 -1
- package/dist/tools/lease-tools.js +22 -8
- package/dist/tools/navigate-arrival.d.ts +0 -2
- package/dist/tools/navigate-arrival.js +1 -1
- package/dist/tools/observe-tools.js +70 -27
- package/dist/tools/read-tools.js +5 -3
- package/dist/tools/real-input-attempt.d.ts +22 -0
- package/dist/tools/real-input-attempt.js +153 -1
- package/dist/tools/reconcile-tools.js +18 -4
- package/dist/tools/resolve-target.d.ts +0 -7
- package/dist/tools/sdk-marker-probe.d.ts +29 -0
- package/dist/tools/sdk-marker-probe.js +57 -0
- package/dist/tools/snapshot-delta.d.ts +1 -1
- package/dist/tools/tool-kit.d.ts +28 -1
- package/dist/tools/tool-kit.js +16 -0
- package/dist/tools/tool-names.d.ts +3 -0
- package/dist/tools/tool-names.js +3 -0
- package/dist/tools/tool-surface.d.ts +85 -6
- package/dist/tools/tool-surface.js +122 -2
- package/dist/tools/tools.js +22 -4
- package/dist/tools/unadvertised-help.js +4 -4
- package/dist/tools/verify-next-baton.d.ts +53 -0
- package/dist/tools/verify-next-baton.js +52 -0
- package/dist/update/update-nudge.d.ts +7 -10
- package/dist/update/update-nudge.js +12 -3
- package/dist/update/updater.d.ts +1 -1
- package/docs/README.md +3 -1
- package/docs/cli/cloud.mdx +1 -1
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/verify.mdx +4 -5
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +3 -3
- package/docs/enterprise.md +3 -1
- package/docs/gates.md +1 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/license-activation.mdx +145 -0
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/server.mdx +1 -1
- package/docs/packages.mdx +8 -6
- package/docs/platform-integration.md +17 -0
- package/docs/predicates.mdx +8 -0
- package/docs/reticle-manual.md +106 -0
- package/docs/telemetry-contract.md +1 -12
- package/docs/telemetry-events.mdx +1 -34
- package/docs/telemetry.md +0 -1
- package/docs/tools-tools-and-run.mdx +2 -4
- package/docs/troubleshooting.mdx +9 -0
- package/docs/usage.md +3 -2
- package/package.json +2 -2
- package/dist/session/session-readiness.d.ts +0 -34
- package/dist/session/session-readiness.js +0 -36
- package/dist/telemetry/instrumentation-stall.d.ts +0 -57
- package/dist/telemetry/instrumentation-stall.js +0 -103
|
@@ -160,8 +160,140 @@ function describeNetFilter(p) {
|
|
|
160
160
|
const { kind: _kind, count: _count, since: _since, ...filter } = p;
|
|
161
161
|
return JSON.stringify(filter);
|
|
162
162
|
}
|
|
163
|
+
/** Same, for a signal: the matcher as applied, with the cardinality and the floor taken out. */
|
|
164
|
+
function describeSignalFilter(p) {
|
|
165
|
+
const { kind: _kind, count: _count, since: _since, ...filter } = p;
|
|
166
|
+
return JSON.stringify(filter);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Exact cardinality, once, for every channel that can count its matches.
|
|
170
|
+
*
|
|
171
|
+
* `net` and `signal` are the same assertion over different evidence — "this happened EXACTLY n
|
|
172
|
+
* times" — and the interesting half is the same on both: an over-count is `decided`, because a
|
|
173
|
+
* window only accumulates and a count cannot come back down, so waiting out the rest of the budget
|
|
174
|
+
* to report a double-fire buys nothing but latency.
|
|
175
|
+
*
|
|
176
|
+
* `noun` names the population in prose ("network call(s)", "signal(s)"), `filter` is the matcher as
|
|
177
|
+
* applied, and `assertion` is the oracle that judged it.
|
|
178
|
+
*/
|
|
179
|
+
function evalExactCount(args) {
|
|
180
|
+
const { matched, want, noun, filter, assertion } = args;
|
|
181
|
+
if (matched === want)
|
|
182
|
+
return { pass: true, evidence: { matched } };
|
|
183
|
+
return {
|
|
184
|
+
pass: false,
|
|
185
|
+
...(matched > want ? { decided: true } : {}),
|
|
186
|
+
failureReason: `expected ${String(want)} ${noun} matching ${filter}, saw ${String(matched)}`,
|
|
187
|
+
observed: `${String(matched)} matching ${noun}`,
|
|
188
|
+
expected: `exactly ${String(want)} matching ${filter}`,
|
|
189
|
+
assertion,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* URL suffixes only the DOCUMENT fetches, which the network observer therefore never records.
|
|
194
|
+
*
|
|
195
|
+
* `network.ts` patches `fetch` and `XMLHttpRequest` and nothing else, so `<link rel=icon>`,
|
|
196
|
+
* `<link rel=manifest>`, stylesheets, fonts and `<img src>` are invisible to it. "No matching call"
|
|
197
|
+
* and "that class of call is not observed" are then indistinguishable, and the miss graded
|
|
198
|
+
* `assertion_failed` — a false RED. Reported from the field: an assert over `/favicon.ico`,
|
|
199
|
+
* `/site.webmanifest` and `/apple-touch-icon.png` returned `verified:"no"` while curl showed all
|
|
200
|
+
* three answering 200. A false negative here is worse than an unknown, because an agent that trusts
|
|
201
|
+
* it goes and "fixes" working code.
|
|
202
|
+
*
|
|
203
|
+
* Deliberately NOT here: `.js` and `.json`. Both are routinely fetched via `fetch`/XHR — a module
|
|
204
|
+
* preload and an API call can share a suffix — so downgrading them would hide real misses. This list
|
|
205
|
+
* is only the suffixes for which the document is the sole plausible initiator.
|
|
206
|
+
*
|
|
207
|
+
* This began as the smaller half of #447: it did not make these requests observable, it stopped
|
|
208
|
+
* Reticle claiming they did not happen. The other half, observing them via resource timing, has
|
|
209
|
+
* since landed in the browser SDK, so the two halves are no longer independent and this downgrade is
|
|
210
|
+
* now GATED: when the event stream carries at least one document-initiated record, the observer is
|
|
211
|
+
* demonstrably live and a miss over these suffixes is real evidence, graded a plain failure. Only a
|
|
212
|
+
* page whose stream holds no resource entry at all keeps the honest "cannot tell apart" answer,
|
|
213
|
+
* because there the observer may simply not exist.
|
|
214
|
+
*/
|
|
215
|
+
const DOCUMENT_ONLY_SUFFIXES = [
|
|
216
|
+
'.ico',
|
|
217
|
+
'.png',
|
|
218
|
+
'.jpg',
|
|
219
|
+
'.jpeg',
|
|
220
|
+
'.gif',
|
|
221
|
+
'.svg',
|
|
222
|
+
'.webp',
|
|
223
|
+
'.avif',
|
|
224
|
+
'.bmp',
|
|
225
|
+
'.css',
|
|
226
|
+
'.woff',
|
|
227
|
+
'.woff2',
|
|
228
|
+
'.ttf',
|
|
229
|
+
'.otf',
|
|
230
|
+
'.eot',
|
|
231
|
+
'.webmanifest',
|
|
232
|
+
];
|
|
233
|
+
/**
|
|
234
|
+
* Does this filter target a class of request the observer cannot see?
|
|
235
|
+
*
|
|
236
|
+
* Read off `urlContains` only, and only when the pattern ENDS in one of the suffixes — a filter of
|
|
237
|
+
* `/api/` that happens to contain `.css` somewhere in a query string is still an ordinary XHR
|
|
238
|
+
* target. Query strings and fragments are stripped first, since `favicon.ico?v=2` is the same asset.
|
|
239
|
+
*/
|
|
240
|
+
function targetsUnobservedChannel(p) {
|
|
241
|
+
if (p.urlContains === undefined)
|
|
242
|
+
return false;
|
|
243
|
+
const path = (p.urlContains.split('#')[0] ?? '').split('?')[0]?.toLowerCase() ?? '';
|
|
244
|
+
return DOCUMENT_ONLY_SUFFIXES.some((suffix) => path.endsWith(suffix));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Initiator types the browser SDK's resource-timing observer stamps onto document-initiated records
|
|
248
|
+
* (packages/browser/src/observers/network.ts). The patched transports sign themselves `fetch`,
|
|
249
|
+
* `xhr` or `beacon`, so any NET_REQUEST carrying one of these came from a `resource` entry, and its
|
|
250
|
+
* mere presence is proof the PerformanceObserver is alive on this page. Mirrors the wire; keep in
|
|
251
|
+
* sync with the browser package.
|
|
252
|
+
*/
|
|
253
|
+
const DOCUMENT_INITIATORS = new Set([
|
|
254
|
+
'link',
|
|
255
|
+
'css',
|
|
256
|
+
'img',
|
|
257
|
+
'script',
|
|
258
|
+
'manifest',
|
|
259
|
+
'other',
|
|
260
|
+
]);
|
|
261
|
+
/**
|
|
262
|
+
* Did the observer actually report a document-initiated load?
|
|
263
|
+
*
|
|
264
|
+
* This is the gate on the unobserved-channel downgrade above. Once subresource observation landed,
|
|
265
|
+
* a miss over `.ico`/`.css`/`.woff2` is no longer inherently unknowable: if the observer is live it
|
|
266
|
+
* would have seen the favicon load, so seeing none is evidence it never fired. But the observer is
|
|
267
|
+
* opt-in-by-engine, not guaranteed: on a renderer without `PerformanceObserver` (or before any
|
|
268
|
+
* resource entry exists) nothing distinguishes "not requested" from "not visible", and the honest
|
|
269
|
+
* answer stays inconclusive. Liveness is inferred from the same events being judged: one
|
|
270
|
+
* document-initiated record anywhere in the window proves the channel works.
|
|
271
|
+
*/
|
|
272
|
+
function observerSawSubresources(events) {
|
|
273
|
+
return events.some((e) => {
|
|
274
|
+
if (e.type !== EventType.NET_REQUEST)
|
|
275
|
+
return false;
|
|
276
|
+
const initiator = str(e.data['initiator']);
|
|
277
|
+
return initiator !== undefined && DOCUMENT_INITIATORS.has(initiator);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
/** The sentence both zero-match branches hand to `inconclusive`. */
|
|
281
|
+
function unobservedChannelReason(p) {
|
|
282
|
+
return (`no call matched ${describeNetFilter(p)}, and no document-initiated load was recorded either, ` +
|
|
283
|
+
`so this page exposed no resource timing to read: Reticle observes fetch and XMLHttpRequest ` +
|
|
284
|
+
`directly, while a request the document initiates (<link rel=icon|manifest|preload>, a ` +
|
|
285
|
+
`stylesheet, a font, <img src>) leaves no record, and that state cannot be told apart from the ` +
|
|
286
|
+
`request having been made. ` +
|
|
287
|
+
`Nothing here says the app is wrong. Check it outside the browser, or assert something the ` +
|
|
288
|
+
`document does not fetch on its own`);
|
|
289
|
+
}
|
|
163
290
|
export function evalNet(events, p) {
|
|
164
291
|
const since = p.since ?? 0;
|
|
292
|
+
/**
|
|
293
|
+
* Computed once: the gate every zero-match downgrade below reads. True means the resource-timing
|
|
294
|
+
* observer is demonstrably live in this window, so a miss is evidence rather than blindness.
|
|
295
|
+
*/
|
|
296
|
+
const sawSubresources = observerSawSubresources(events);
|
|
165
297
|
/**
|
|
166
298
|
* Did any call match everything EXCEPT the body assertion, while carrying no recorded body?
|
|
167
299
|
*
|
|
@@ -171,6 +303,8 @@ export function evalNet(events, p) {
|
|
|
171
303
|
* because it is the same pass over the same events.
|
|
172
304
|
*/
|
|
173
305
|
let matchedButUnrecorded = false;
|
|
306
|
+
/** A call matched url/method but carried NO readable status (document-initiated subresource). */
|
|
307
|
+
let unobservableStatus = false;
|
|
174
308
|
/**
|
|
175
309
|
* The response body of a call that matched everything EXCEPT the body assertion, and HAD one.
|
|
176
310
|
*
|
|
@@ -190,8 +324,19 @@ export function evalNet(events, p) {
|
|
|
190
324
|
if (p.urlContains !== undefined && !(str(d['url']) ?? '').includes(p.urlContains)) {
|
|
191
325
|
return false;
|
|
192
326
|
}
|
|
193
|
-
if (p.status !== undefined
|
|
194
|
-
|
|
327
|
+
if (p.status !== undefined) {
|
|
328
|
+
const status = num(d['status']);
|
|
329
|
+
if (status === undefined) {
|
|
330
|
+
// Document-initiated subresources (link/css/img/manifest via resource timing) carry no
|
|
331
|
+
// readable status on engines without responseStatus. A failed assertion here would read
|
|
332
|
+
// "your change is broken" and send the caller to fix working code — the exact false
|
|
333
|
+
// negative the oracle exists to prevent. Downgrade to unknown instead.
|
|
334
|
+
unobservableStatus = true;
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
if (status !== p.status)
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
195
340
|
if (p.ok !== undefined && callSucceeded(d) !== p.ok)
|
|
196
341
|
return false;
|
|
197
342
|
if (p.bodyContains !== undefined) {
|
|
@@ -211,6 +356,18 @@ export function evalNet(events, p) {
|
|
|
211
356
|
}
|
|
212
357
|
return true;
|
|
213
358
|
});
|
|
359
|
+
if (unobservableStatus && 0 === matches.length) {
|
|
360
|
+
// The url/method matched a document-initiated subresource whose engine could not read a status.
|
|
361
|
+
// "No matching call" would be a lie in both directions: it may have succeeded, it may have 404'd
|
|
362
|
+
// on exactly the path mistake the caller is hunting. Say the truth — not observable here.
|
|
363
|
+
return {
|
|
364
|
+
pass: false,
|
|
365
|
+
inconclusive: `a document-initiated request matching ${describeNetFilter(p)} was observed, but this engine does not expose its status code (resource timing without responseStatus) — assert on the element or route instead, or check the network tab`,
|
|
366
|
+
observed: 'a matching request with no readable status',
|
|
367
|
+
expected: `a status of ${String(p.status)} on ${JSON.stringify(p.urlContains ?? '*')}`,
|
|
368
|
+
assertion: 'net.unobservable-status',
|
|
369
|
+
};
|
|
370
|
+
}
|
|
214
371
|
if (matchedButUnrecorded && 0 === matches.length) {
|
|
215
372
|
return {
|
|
216
373
|
pass: false,
|
|
@@ -235,21 +392,37 @@ export function evalNet(events, p) {
|
|
|
235
392
|
// useEffect-double-fire / retry-storm regression class, where the request DID fire (presence passes)
|
|
236
393
|
// but fired the WRONG number of times. Without `count`, the matcher is presence-only (≥1).
|
|
237
394
|
if (p.count !== undefined) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
395
|
+
if (matches.length !== p.count &&
|
|
396
|
+
0 === matches.length &&
|
|
397
|
+
targetsUnobservedChannel(p) &&
|
|
398
|
+
!sawSubresources) {
|
|
399
|
+
return {
|
|
241
400
|
pass: false,
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// also the one that took the caller's whole budget to report.
|
|
245
|
-
...(matches.length > p.count ? { decided: true } : {}),
|
|
246
|
-
failureReason: `expected ${String(p.count)} network call(s) matching ${describeNetFilter(p)}, saw ${String(matches.length)}`,
|
|
247
|
-
observed: `${String(matches.length)} matching network call(s)`,
|
|
248
|
-
expected: `exactly ${String(p.count)} matching ${describeNetFilter(p)}`,
|
|
401
|
+
failureReason: unobservedChannelReason(p),
|
|
402
|
+
inconclusive: unobservedChannelReason(p),
|
|
249
403
|
assertion: 'net.count',
|
|
250
404
|
};
|
|
405
|
+
}
|
|
406
|
+
return evalExactCount({
|
|
407
|
+
matched: matches.length,
|
|
408
|
+
want: p.count,
|
|
409
|
+
noun: 'network call(s)',
|
|
410
|
+
filter: describeNetFilter(p),
|
|
411
|
+
assertion: 'net.count',
|
|
412
|
+
});
|
|
251
413
|
}
|
|
252
414
|
const hit = matches[0];
|
|
415
|
+
if (hit === undefined && targetsUnobservedChannel(p) && !sawSubresources) {
|
|
416
|
+
const reason = unobservedChannelReason(p);
|
|
417
|
+
return {
|
|
418
|
+
pass: false,
|
|
419
|
+
failureReason: reason,
|
|
420
|
+
inconclusive: reason,
|
|
421
|
+
observed: describeObserved('calls', events.filter((e) => e.type === EventType.NET_REQUEST).map(describeCall)),
|
|
422
|
+
expected: `at least one call matching ${describeNetFilter(p)}`,
|
|
423
|
+
assertion: 'net.unobserved-channel',
|
|
424
|
+
};
|
|
425
|
+
}
|
|
253
426
|
return hit !== undefined
|
|
254
427
|
? { pass: true, evidence: hit.data }
|
|
255
428
|
: {
|
|
@@ -296,25 +469,66 @@ export function evalConsole(events, p) {
|
|
|
296
469
|
return isErr;
|
|
297
470
|
return e.type === CONSOLE_LEVEL_TYPE[p.level];
|
|
298
471
|
});
|
|
472
|
+
// A text match narrows the population to entries whose captured message contains the substring.
|
|
473
|
+
// With `absent: true` this is the whole point: "THIS message did not appear", not "no messages
|
|
474
|
+
// appeared" — the difference between a regression check and a fragile one that any unrelated
|
|
475
|
+
// warning anywhere in the app breaks.
|
|
476
|
+
const wanted = 'contains' in p ? p.contains : undefined;
|
|
477
|
+
// Captured messages are strings (stringifyArgs in the browser observer), but a malformed or
|
|
478
|
+
// foreign event must not crash the evaluator: non-strings stringify defensively, and objects
|
|
479
|
+
// go through JSON.stringify rather than a default toString that would print '[object Object]'.
|
|
480
|
+
const asText = (v) => {
|
|
481
|
+
if ('string' === typeof v)
|
|
482
|
+
return v;
|
|
483
|
+
try {
|
|
484
|
+
return JSON.stringify(v) ?? '';
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
return '';
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
const matching = wanted !== undefined
|
|
491
|
+
? matches.filter((e) => asText(e.data['message']).includes(wanted))
|
|
492
|
+
: matches;
|
|
299
493
|
if (true === p.absent) {
|
|
300
|
-
|
|
494
|
+
if (wanted !== undefined && 0 === matching.length && matches.length > 0) {
|
|
495
|
+
// Other entries exist but none carries the substring: exactly the pass an absence-with-match
|
|
496
|
+
// asserts. Name both counts so the caller can tell this from a silent window.
|
|
497
|
+
return {
|
|
498
|
+
pass: true,
|
|
499
|
+
evidence: { absent: true, contains: wanted },
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
return 0 === matching.length
|
|
301
503
|
? { pass: true, evidence: { absent: true } }
|
|
302
504
|
: {
|
|
303
505
|
pass: false,
|
|
304
|
-
failureReason:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
506
|
+
failureReason: wanted !== undefined
|
|
507
|
+
? `expected no ${p.level ?? 'console'} entry containing ${JSON.stringify(wanted)} but found ${String(matching.length)}`
|
|
508
|
+
: `expected no ${p.level ?? 'console'} entries but found ${String(matches.length)}`,
|
|
509
|
+
observed: wanted !== undefined
|
|
510
|
+
? `${String(matching.length)} ${p.level ?? 'console'} entr${1 === matching.length ? 'y' : 'ies'} containing ${JSON.stringify(wanted)}`
|
|
511
|
+
: `${String(matches.length)} ${p.level ?? 'console'} entr${1 === matches.length ? 'y' : 'ies'}`,
|
|
512
|
+
expected: wanted !== undefined
|
|
513
|
+
? `no ${p.level ?? 'console'} entry containing ${JSON.stringify(wanted)}`
|
|
514
|
+
: `no ${p.level ?? 'console'} entries`,
|
|
515
|
+
assertion: wanted !== undefined ? 'console.absent-contains' : 'console.absent',
|
|
516
|
+
evidence: matching.map((e) => e.data),
|
|
309
517
|
};
|
|
310
518
|
}
|
|
311
|
-
return
|
|
312
|
-
? { pass: true, evidence:
|
|
519
|
+
return matching.length > 0
|
|
520
|
+
? { pass: true, evidence: matching.map((e) => e.data) }
|
|
313
521
|
: {
|
|
314
522
|
pass: false,
|
|
315
|
-
failureReason:
|
|
316
|
-
|
|
317
|
-
|
|
523
|
+
failureReason: wanted !== undefined
|
|
524
|
+
? `no ${p.level ?? 'console'} entry containing ${JSON.stringify(wanted)} found`
|
|
525
|
+
: `no ${p.level ?? 'console'} entries found`,
|
|
526
|
+
observed: wanted !== undefined
|
|
527
|
+
? `no ${p.level ?? 'console'} entry containing ${JSON.stringify(wanted)} in the window`
|
|
528
|
+
: `no ${p.level ?? 'console'} entries in the window`,
|
|
529
|
+
expected: wanted !== undefined
|
|
530
|
+
? `at least one ${p.level ?? 'console'} entry containing ${JSON.stringify(wanted)}`
|
|
531
|
+
: `at least one ${p.level ?? 'console'} entry`,
|
|
318
532
|
assertion: 'console.present',
|
|
319
533
|
};
|
|
320
534
|
}
|
|
@@ -340,7 +554,7 @@ export function evalAnimation(events, p) {
|
|
|
340
554
|
};
|
|
341
555
|
}
|
|
342
556
|
export function evalSignal(events, p) {
|
|
343
|
-
const
|
|
557
|
+
const isMatch = (e) => {
|
|
344
558
|
if (e.type !== EventType.SIGNAL)
|
|
345
559
|
return false;
|
|
346
560
|
if (p.name !== undefined && str(e.data['name']) !== p.name)
|
|
@@ -351,7 +565,22 @@ export function evalSignal(events, p) {
|
|
|
351
565
|
return false;
|
|
352
566
|
}
|
|
353
567
|
return true;
|
|
354
|
-
}
|
|
568
|
+
};
|
|
569
|
+
// `count` (exact) turns presence into a cardinality assertion, exactly as it does on `net`. The
|
|
570
|
+
// double-fire is invisible to every state-only oracle — a handler wired twice leaves the store in
|
|
571
|
+
// the right shape and fires the signal twice — and so is the wrong-name fire, where the intended
|
|
572
|
+
// signal fires once beside a mistyped sibling and a presence check cannot say which is which.
|
|
573
|
+
// Counting only what the MATCHER matched is what separates them. Omit = presence (≥1).
|
|
574
|
+
if (p.count !== undefined) {
|
|
575
|
+
return evalExactCount({
|
|
576
|
+
matched: events.filter(isMatch).length,
|
|
577
|
+
want: p.count,
|
|
578
|
+
noun: 'signal(s)',
|
|
579
|
+
filter: describeSignalFilter(p),
|
|
580
|
+
assertion: 'signal.count',
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
const hit = events.find(isMatch);
|
|
355
584
|
if (hit !== undefined)
|
|
356
585
|
return { pass: true, evidence: hit.data };
|
|
357
586
|
// Near-miss: show signals that fired with the same name (so the agent sees the real data).
|
|
@@ -14,10 +14,4 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { z } from 'zod';
|
|
16
16
|
import { PredicateSchema } from './predicate-eval.js';
|
|
17
|
-
/**
|
|
18
|
-
* Parse a predicate, or throw an Error whose message is a sentence.
|
|
19
|
-
*
|
|
20
|
-
* Deliberately still THROWS: every call site already handles a throw, and turning this into a
|
|
21
|
-
* result type would mean touching three handlers to gain nothing the message does not already say.
|
|
22
|
-
*/
|
|
23
17
|
export declare function parsePredicate(input: unknown): z.infer<typeof PredicateSchema>;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { z } from 'zod';
|
|
16
16
|
import { PredicateKind } from '@reticlehq/core';
|
|
17
|
-
import { PredicateSchema, predicateFieldsFor } from './predicate-eval.js';
|
|
17
|
+
import { PredicateSchema, predicateFieldsFor, predicateNestedFieldsFor } from './predicate-eval.js';
|
|
18
18
|
/**
|
|
19
19
|
* One valid call PER KIND, because an example of another kind answers a question nobody asked.
|
|
20
20
|
*
|
|
@@ -67,7 +67,35 @@ function describeIssue(issue) {
|
|
|
67
67
|
* Deliberately still THROWS: every call site already handles a throw, and turning this into a
|
|
68
68
|
* result type would mean touching three handlers to gain nothing the message does not already say.
|
|
69
69
|
*/
|
|
70
|
+
/**
|
|
71
|
+
* The placeholder `verify_next` puts in the `until` it suggests, sent back unchanged.
|
|
72
|
+
*
|
|
73
|
+
* The nudge hands the agent a ready-made `act_and_wait` call with `ref` and `action` filled in from
|
|
74
|
+
* the act that dispatched, and leaves the consequence blank on purpose: naming it is the one part
|
|
75
|
+
* only the agent can know, and guessing would be Reticle inventing the assertion.
|
|
76
|
+
*
|
|
77
|
+
* Sent verbatim it parses as a perfectly valid text predicate that simply never matches, so the
|
|
78
|
+
* verdict came back `verified:"no"` / "the declared consequence did not hold" — blaming the app for
|
|
79
|
+
* a placeholder the agent forgot to replace, and sending it to hunt a defect that does not exist.
|
|
80
|
+
* Measured on a live app before this guard: a confident, wrong, actionable-looking answer, which is
|
|
81
|
+
* the exact failure the rest of this file exists to avoid.
|
|
82
|
+
*/
|
|
83
|
+
const UNFILLED_PLACEHOLDER = /^<name the consequence/i;
|
|
84
|
+
function placeholderValue(input) {
|
|
85
|
+
if ('object' !== typeof input || null === input)
|
|
86
|
+
return undefined;
|
|
87
|
+
const value = input['value'];
|
|
88
|
+
return 'string' === typeof value && UNFILLED_PLACEHOLDER.test(value) ? value : undefined;
|
|
89
|
+
}
|
|
70
90
|
export function parsePredicate(input) {
|
|
91
|
+
const unfilled = placeholderValue(input);
|
|
92
|
+
if (unfilled !== undefined) {
|
|
93
|
+
throw new Error(`that predicate still carries the placeholder from verify_next ("${unfilled}"). Nothing ran, ` +
|
|
94
|
+
'and no verdict was produced — which is deliberate: as written it would have failed and ' +
|
|
95
|
+
'blamed the app for a value you had not filled in yet. Replace it with the consequence ' +
|
|
96
|
+
'this action actually causes, and prefer one the action CHANGES: a signal, a request, a ' +
|
|
97
|
+
'route, or store state. Text on screen that was already there proves nothing.');
|
|
98
|
+
}
|
|
71
99
|
const parsed = PredicateSchema.safeParse(input);
|
|
72
100
|
if (parsed.success)
|
|
73
101
|
return parsed.data;
|
|
@@ -78,7 +106,7 @@ export function parsePredicate(input) {
|
|
|
78
106
|
// back is how the zod array became unreadable in the first place.
|
|
79
107
|
const issues = parsed.error.issues.slice(0, 3).map(describeIssue).join('; ');
|
|
80
108
|
throw new Error(`that predicate did not parse (kind "${kind}"): ${issues}. Nothing ran — the predicate was ` +
|
|
81
|
-
`not evaluated, so no verdict was produced. ${accepted(kind)} ` +
|
|
109
|
+
`not evaluated, so no verdict was produced. ${accepted(kind, parsed.error.issues)} ` +
|
|
82
110
|
`A valid ${kind} predicate looks like: ${exampleFor(kind)}`);
|
|
83
111
|
}
|
|
84
112
|
/**
|
|
@@ -88,9 +116,27 @@ export function parsePredicate(input) {
|
|
|
88
116
|
* round trip on the one call path that produces verdicts. Naming the accepted fields — or, when the
|
|
89
117
|
* kind itself is the mistake, the accepted kinds — makes the retry informed instead.
|
|
90
118
|
*/
|
|
91
|
-
function accepted(kind) {
|
|
119
|
+
function accepted(kind, issues) {
|
|
92
120
|
const fields = predicateFieldsFor(kind);
|
|
93
|
-
if (0 < fields.length)
|
|
94
|
-
|
|
121
|
+
if (0 < fields.length) {
|
|
122
|
+
// A field whose value is an object is the one an agent cannot guess from its name alone, so
|
|
123
|
+
// expand it in the same breath rather than making the shape a second round trip.
|
|
124
|
+
//
|
|
125
|
+
// Only the field the rejection actually points at, though. Expanding every object-valued field
|
|
126
|
+
// of the kind makes the sentence longer the more the schema grows, and buries the one clause
|
|
127
|
+
// that answers the question asked — the same argument this file already makes for showing one
|
|
128
|
+
// example per kind instead of a generic one. `element` is the only kind with an object-valued
|
|
129
|
+
// field today, so the two agree except when the mistake is somewhere else entirely, which is
|
|
130
|
+
// the case pinned in the tests.
|
|
131
|
+
const all = predicateNestedFieldsFor(kind);
|
|
132
|
+
const blamed = new Set(issues
|
|
133
|
+
.map((issue) => issue.path[0])
|
|
134
|
+
.filter((field) => 'string' === typeof field));
|
|
135
|
+
const nested = Object.entries(all)
|
|
136
|
+
.filter(([field]) => blamed.has(field))
|
|
137
|
+
.map(([field, keys]) => ` ${field} accepts: ${keys.join(', ')}.`)
|
|
138
|
+
.join('');
|
|
139
|
+
return `${kind} accepts: ${fields.join(', ')}.${nested}`;
|
|
140
|
+
}
|
|
95
141
|
return `"${kind}" is not a predicate kind — use one of: ${Object.values(PredicateKind).join(', ')}.`;
|
|
96
142
|
}
|
|
@@ -38,6 +38,12 @@ export type Predicate = {
|
|
|
38
38
|
} | {
|
|
39
39
|
kind: typeof PredicateKind.CONSOLE;
|
|
40
40
|
level?: string;
|
|
41
|
+
/**
|
|
42
|
+
* A substring the captured message must contain. With `absent: true` this flips the meaning
|
|
43
|
+
* from "no console entries at all" to "THIS message did not appear" — the assertion people
|
|
44
|
+
* actually write regression checks for (deprecation warnings, no-op handler notices).
|
|
45
|
+
*/
|
|
46
|
+
contains?: string;
|
|
41
47
|
absent?: boolean;
|
|
42
48
|
since?: number;
|
|
43
49
|
} | {
|
|
@@ -50,6 +56,11 @@ export type Predicate = {
|
|
|
50
56
|
kind: typeof PredicateKind.SIGNAL;
|
|
51
57
|
name?: string;
|
|
52
58
|
dataMatches?: Record<string, unknown>;
|
|
59
|
+
/**
|
|
60
|
+
* Exact number of matching signals in the window — the same cardinality assertion `net`
|
|
61
|
+
* carries, on the channel the app itself speaks. Omit for presence (≥1).
|
|
62
|
+
*/
|
|
63
|
+
count?: number;
|
|
53
64
|
since?: number;
|
|
54
65
|
} | {
|
|
55
66
|
kind: typeof PredicateKind.STATE;
|
|
@@ -97,3 +108,54 @@ export declare const PredicateSchema: z.ZodType<Predicate>;
|
|
|
97
108
|
* wall. Empty for a kind that is not in the union.
|
|
98
109
|
*/
|
|
99
110
|
export declare function predicateFieldsFor(kind: string): readonly string[];
|
|
111
|
+
/**
|
|
112
|
+
* Is this tool parameter ACTUALLY the predicate union?
|
|
113
|
+
*
|
|
114
|
+
* Not a name check. `until` is overloaded on this surface — the act/assert family means a predicate
|
|
115
|
+
* by it, while reticle_observe / _network / _console mean a NUMBER, an upper cursor bound — so any
|
|
116
|
+
* caller keying on the word would treat a numeric parameter as a predicate and describe it to the
|
|
117
|
+
* agent as an object. Keying on the schema cannot make that mistake.
|
|
118
|
+
*
|
|
119
|
+
* Lives beside the schema because two callers now ask the question: the lean tool surface, which
|
|
120
|
+
* compacts these parameters, and `reticle_tools`, which spells their grammar out on request. Two
|
|
121
|
+
* spellings of "is this a predicate" is one chance for the surface and the grammar to disagree.
|
|
122
|
+
*/
|
|
123
|
+
export declare function isPredicateParam(schema: z.ZodTypeAny): boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Every kind's fields, in one block an agent can write a predicate from.
|
|
126
|
+
*
|
|
127
|
+
* The tool surface advertises the KIND list and points at `reticle_tools` for the fields, because
|
|
128
|
+
* inlining the 12-variant recursive union in the declared JSON Schema costs thousands of characters
|
|
129
|
+
* per predicate parameter, re-sent every turn, to describe a grammar most calls use one variant of.
|
|
130
|
+
* That trade is right — but the pointer has to land somewhere, and it landed on a parameter
|
|
131
|
+
* description reading "same shape as reticle_assert". So the grammar was reachable from nothing, and
|
|
132
|
+
* an agent that could not find `route`'s fields fell back to a `text` check where a route check was
|
|
133
|
+
* meant: the weaker oracle, which is the expensive half of an undiscoverable grammar.
|
|
134
|
+
*
|
|
135
|
+
* Derived from the schema, like `predicateFieldsFor` and for the same reason: a hand-written grammar
|
|
136
|
+
* that drifts is worse than none, because the agent trusts it and retries into the same wall.
|
|
137
|
+
*/
|
|
138
|
+
export declare function predicateGrammar(): Readonly<Record<string, string>>;
|
|
139
|
+
/**
|
|
140
|
+
* The keys of a field that is an object, once its wrappers are peeled. Empty for anything else.
|
|
141
|
+
*
|
|
142
|
+
* Exported so the wrapper handling is asserted against THIS function rather than through the
|
|
143
|
+
* rendered sentence. Reached only that way, a wrapper it fails to peel returns `[]`, the old
|
|
144
|
+
* message prints unchanged, and nothing reddens — a regression with no symptom.
|
|
145
|
+
*
|
|
146
|
+
* No top-level predicate field is currently declared behind a wrapper, so the real schema exercises
|
|
147
|
+
* none of these paths; that is exactly why they are worth a case each.
|
|
148
|
+
*/
|
|
149
|
+
export declare function nestedKeysOf(schema: z.ZodTypeAny | undefined): readonly string[];
|
|
150
|
+
/**
|
|
151
|
+
* One level into the fields that are themselves objects — `{ query: ['by', 'value', ...] }`.
|
|
152
|
+
*
|
|
153
|
+
* Naming the top-level fields alone leaves `element` unguessable: `query` is an object with its own
|
|
154
|
+
* required shape, and a plain CSS string works in `reticle_snapshot.scope` and `reticle_query.scope`,
|
|
155
|
+
* so assuming it works here is the natural guess. The rejection is the only place that inconsistency
|
|
156
|
+
* can be explained.
|
|
157
|
+
*
|
|
158
|
+
* One level only. Derived from the schema for the same reason `predicateFieldsFor` is: a stale field
|
|
159
|
+
* list is worse than none, because the agent trusts it and retries into the same wall.
|
|
160
|
+
*/
|
|
161
|
+
export declare function predicateNestedFieldsFor(kind: string): Readonly<Record<string, readonly string[]>>;
|