@reticlehq/server 2.2.0 → 2.3.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/README.md +1 -1
- package/dist/bridge-security.d.ts +0 -1
- package/dist/bridge-security.js +0 -1
- package/dist/bridge.d.ts +0 -1
- package/dist/bridge.js +60 -10
- package/dist/bridge.test-harness.d.ts +0 -1
- package/dist/bridge.test-harness.js +0 -1
- package/dist/capsule/capsule-store.d.ts +2 -3
- package/dist/capsule/capsule-store.js +0 -1
- package/dist/capsule/capsule.d.ts +0 -1
- package/dist/capsule/capsule.js +0 -1
- package/dist/capsule/causal-summary.d.ts +21 -1
- package/dist/capsule/causal-summary.js +20 -2
- package/dist/capsule/divergence.d.ts +0 -1
- package/dist/capsule/divergence.js +0 -1
- package/dist/capsule/minimize.d.ts +0 -1
- package/dist/capsule/minimize.js +0 -1
- package/dist/capsule/predicate-to-links.d.ts +0 -1
- package/dist/capsule/predicate-to-links.js +0 -1
- package/dist/cli-doctor.d.ts +6 -0
- package/dist/cli-doctor.js +60 -0
- package/dist/cli-flow-commands.d.ts +0 -1
- package/dist/cli-flow-commands.js +0 -1
- package/dist/cli-launch.d.ts +0 -1
- package/dist/cli-launch.js +7 -3
- package/dist/cli-parse.d.ts +52 -2
- package/dist/cli-parse.js +147 -3
- package/dist/cli-port.d.ts +0 -1
- package/dist/cli-port.js +0 -1
- package/dist/cli-verify.d.ts +2 -1
- package/dist/cli-verify.js +24 -3
- package/dist/cli.d.ts +2 -4
- package/dist/cli.js +109 -10
- package/dist/cloud/cloud-config.d.ts +36 -0
- package/dist/cloud/cloud-config.js +86 -0
- package/dist/cloud/cloud-sync.d.ts +96 -4
- package/dist/cloud/cloud-sync.js +131 -3
- package/dist/cloud-cli.d.ts +3 -0
- package/dist/cloud-cli.js +514 -0
- package/dist/crawl/crawl-tools.d.ts +0 -1
- package/dist/crawl/crawl-tools.js +1 -2
- package/dist/crawl/crawl.d.ts +17 -3
- package/dist/crawl/crawl.js +43 -4
- package/dist/daemon-resilience.d.ts +6 -1
- package/dist/daemon-resilience.js +63 -1
- package/dist/daemon.d.ts +23 -3
- package/dist/daemon.js +78 -21
- package/dist/domain/domain-model.d.ts +0 -1
- package/dist/domain/domain-model.js +1 -2
- package/dist/domain/domain-tools.d.ts +0 -7
- package/dist/domain/domain-tools.js +21 -4
- package/dist/domain/flow-risk.d.ts +0 -1
- package/dist/domain/flow-risk.js +0 -1
- package/dist/ee/audit-log.d.ts +0 -1
- package/dist/ee/audit-log.js +0 -1
- package/dist/events/body-failures.d.ts +4 -0
- package/dist/events/body-failures.js +113 -0
- package/dist/events/contradictions.d.ts +53 -0
- package/dist/events/contradictions.js +282 -0
- package/dist/events/echo-mismatch.d.ts +4 -0
- package/dist/events/echo-mismatch.js +138 -0
- package/dist/events/event-filters.d.ts +17 -3
- package/dist/events/event-filters.js +13 -3
- package/dist/events/json-salvage.d.ts +32 -0
- package/dist/events/json-salvage.js +96 -0
- package/dist/events/predicate-eval.d.ts +2 -1
- package/dist/events/predicate-eval.js +56 -9
- package/dist/events/predicate.d.ts +0 -1
- package/dist/events/predicate.js +56 -2
- package/dist/events/reaction.d.ts +0 -1
- package/dist/events/reaction.js +0 -1
- package/dist/events/reconcile.d.ts +39 -0
- package/dist/events/reconcile.js +211 -0
- package/dist/events/ring-buffer.d.ts +0 -1
- package/dist/events/ring-buffer.js +0 -1
- package/dist/events/stale-response.d.ts +4 -0
- package/dist/events/stale-response.js +139 -0
- package/dist/events/unit-mismatch.d.ts +16 -0
- package/dist/events/unit-mismatch.js +167 -0
- package/dist/flows/affected-tools.d.ts +18 -0
- package/dist/flows/affected-tools.js +52 -0
- package/dist/flows/affected.d.ts +0 -1
- package/dist/flows/affected.js +0 -1
- package/dist/flows/annotate-tools.d.ts +0 -1
- package/dist/flows/annotate-tools.js +21 -3
- package/dist/flows/annotate.d.ts +0 -1
- package/dist/flows/annotate.js +0 -1
- package/dist/flows/annotation-store.d.ts +0 -1
- package/dist/flows/annotation-store.js +0 -1
- package/dist/flows/assertion-integrity.d.ts +0 -1
- package/dist/flows/assertion-integrity.js +0 -1
- package/dist/flows/assertion-tiers-store.d.ts +0 -1
- package/dist/flows/assertion-tiers-store.js +0 -1
- package/dist/flows/buddy-status.d.ts +0 -1
- package/dist/flows/buddy-status.js +0 -1
- package/dist/flows/coverage.d.ts +0 -1
- package/dist/flows/coverage.js +0 -1
- package/dist/flows/decision.d.ts +0 -1
- package/dist/flows/decision.js +0 -1
- package/dist/flows/flake-store.d.ts +0 -1
- package/dist/flows/flake-store.js +0 -1
- package/dist/flows/flake.d.ts +0 -1
- package/dist/flows/flake.js +0 -1
- package/dist/flows/flow-classify.d.ts +0 -1
- package/dist/flows/flow-classify.js +0 -1
- package/dist/flows/flow-replay-run.d.ts +0 -1
- package/dist/flows/flow-replay-run.js +24 -8
- package/dist/flows/flow-replay.d.ts +20 -4
- package/dist/flows/flow-replay.js +9 -43
- package/dist/flows/flow-report.d.ts +0 -1
- package/dist/flows/flow-report.js +0 -1
- package/dist/flows/flow-scope.d.ts +0 -1
- package/dist/flows/flow-scope.js +0 -1
- package/dist/flows/flow-sources.d.ts +0 -1
- package/dist/flows/flow-sources.js +0 -1
- package/dist/flows/flow-step-runners.d.ts +23 -0
- package/dist/flows/flow-step-runners.js +102 -0
- package/dist/flows/flow-success.d.ts +0 -1
- package/dist/flows/flow-success.js +0 -1
- package/dist/flows/flow-tools.d.ts +0 -1
- package/dist/flows/flow-tools.js +54 -16
- package/dist/flows/flows.d.ts +6 -2
- package/dist/flows/flows.js +15 -2
- package/dist/flows/gate.d.ts +0 -1
- package/dist/flows/gate.js +0 -1
- package/dist/flows/git-changed.d.ts +0 -1
- package/dist/flows/git-changed.js +0 -1
- package/dist/flows/heal-run.d.ts +0 -1
- package/dist/flows/heal-run.js +0 -1
- package/dist/flows/heal.d.ts +0 -1
- package/dist/flows/heal.js +0 -1
- package/dist/flows/parallel-suite.d.ts +0 -1
- package/dist/flows/parallel-suite.js +0 -1
- package/dist/flows/recordings.d.ts +0 -1
- package/dist/flows/recordings.js +0 -1
- package/dist/flows/replay.d.ts +0 -1
- package/dist/flows/replay.js +71 -4
- package/dist/flows/server-verify.d.ts +15 -0
- package/dist/flows/server-verify.js +42 -0
- package/dist/flows/verify-change-tools.d.ts +2 -0
- package/dist/flows/verify-change-tools.js +187 -0
- package/dist/flows/watch-batcher.d.ts +0 -1
- package/dist/flows/watch-batcher.js +0 -1
- package/dist/honesty/accepted-write.d.ts +14 -0
- package/dist/honesty/accepted-write.js +17 -0
- package/dist/honesty/blind-spots.d.ts +18 -1
- package/dist/honesty/blind-spots.js +47 -5
- package/dist/honesty/honesty.d.ts +0 -1
- package/dist/honesty/honesty.js +0 -1
- package/dist/honesty/uncaptured-bodies.d.ts +19 -0
- package/dist/honesty/uncaptured-bodies.js +30 -0
- package/dist/honesty/unread-outcome.d.ts +2 -0
- package/dist/honesty/unread-outcome.js +50 -0
- package/dist/honesty/verified.d.ts +40 -0
- package/dist/honesty/verified.js +86 -0
- package/dist/http-server.d.ts +0 -1
- package/dist/http-server.js +5 -1
- package/dist/hunt/hunt-report.d.ts +50 -0
- package/dist/hunt/hunt-report.js +84 -0
- package/dist/idle-shutdown.d.ts +0 -1
- package/dist/idle-shutdown.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/init/agent-rules.d.ts +0 -1
- package/dist/init/agent-rules.js +4 -2
- package/dist/init/cursor.d.ts +0 -1
- package/dist/init/cursor.js +0 -1
- package/dist/init/desktop-doctor.d.ts +40 -0
- package/dist/init/desktop-doctor.js +198 -0
- package/dist/init/detect.d.ts +0 -1
- package/dist/init/detect.js +0 -1
- package/dist/init/mcp.d.ts +0 -1
- package/dist/init/mcp.js +0 -1
- package/dist/init/node-io.d.ts +0 -1
- package/dist/init/node-io.js +0 -1
- package/dist/init/plan.d.ts +4 -1
- package/dist/init/plan.js +23 -5
- package/dist/init/project-id.d.ts +0 -1
- package/dist/init/project-id.js +0 -1
- package/dist/init/run.d.ts +0 -1
- package/dist/init/run.js +31 -3
- package/dist/init/snippets.d.ts +5 -1
- package/dist/init/snippets.js +5 -1
- package/dist/init/vite-config.d.ts +0 -1
- package/dist/init/vite-config.js +0 -1
- package/dist/input/network-detail.d.ts +0 -1
- package/dist/input/network-detail.js +0 -1
- package/dist/input/network-mock-tools.d.ts +0 -1
- package/dist/input/network-mock-tools.js +3 -4
- package/dist/input/network-mock.d.ts +0 -1
- package/dist/input/network-mock.js +0 -1
- package/dist/input/real-input.d.ts +14 -1
- package/dist/input/real-input.js +45 -3
- package/dist/input/scroll-find.d.ts +0 -1
- package/dist/input/scroll-find.js +0 -1
- package/dist/input/scroll-tools.d.ts +0 -1
- package/dist/input/scroll-tools.js +0 -1
- package/dist/input/viewport-tools.d.ts +0 -1
- package/dist/input/viewport-tools.js +3 -4
- package/dist/journal/ambient-store.d.ts +0 -1
- package/dist/journal/ambient-store.js +0 -1
- package/dist/journal/ambient.d.ts +0 -1
- package/dist/journal/ambient.js +0 -1
- package/dist/journal/attach-journal.d.ts +0 -1
- package/dist/journal/attach-journal.js +0 -1
- package/dist/journal/deviation-report.d.ts +0 -1
- package/dist/journal/deviation-report.js +0 -1
- package/dist/journal/deviation-service.d.ts +0 -1
- package/dist/journal/deviation-service.js +0 -1
- package/dist/journal/envelope-store.d.ts +0 -1
- package/dist/journal/envelope-store.js +0 -1
- package/dist/journal/envelope.d.ts +8 -9
- package/dist/journal/envelope.js +0 -1
- package/dist/journal/journal-query.d.ts +0 -1
- package/dist/journal/journal-query.js +0 -1
- package/dist/journal/journal-recorder.d.ts +0 -1
- package/dist/journal/journal-recorder.js +0 -1
- package/dist/journal/retention.d.ts +0 -1
- package/dist/journal/retention.js +0 -1
- package/dist/journal/rollups.d.ts +0 -1
- package/dist/journal/rollups.js +0 -1
- package/dist/journal/session-end.d.ts +0 -1
- package/dist/journal/session-end.js +0 -1
- package/dist/journal/session-journal.d.ts +0 -1
- package/dist/journal/session-journal.js +0 -1
- package/dist/license/license.d.ts +0 -1
- package/dist/license/license.js +0 -1
- package/dist/log.d.ts +0 -1
- package/dist/log.js +0 -1
- package/dist/mcp-proxy.d.ts +0 -1
- package/dist/mcp-proxy.js +0 -1
- package/dist/mcp.d.ts +15 -1
- package/dist/mcp.js +145 -17
- package/dist/oracles/flow-instrument-gaps.d.ts +0 -1
- package/dist/oracles/flow-instrument-gaps.js +0 -1
- package/dist/oracles/propose-consequences.d.ts +0 -1
- package/dist/oracles/propose-consequences.js +0 -1
- package/dist/oracles/self-instrument.d.ts +0 -1
- package/dist/oracles/self-instrument.js +0 -1
- package/dist/pairing-token.d.ts +0 -1
- package/dist/pairing-token.js +0 -1
- package/dist/phenomena/phenomena.d.ts +0 -1
- package/dist/phenomena/phenomena.js +0 -1
- package/dist/pool/browser-pool.d.ts +0 -1
- package/dist/pool/browser-pool.js +0 -1
- package/dist/pool/lease-reaper.d.ts +0 -1
- package/dist/pool/lease-reaper.js +0 -1
- package/dist/pool/playwright-launcher.d.ts +2 -1
- package/dist/pool/playwright-launcher.js +22 -2
- package/dist/project/baselines.d.ts +0 -1
- package/dist/project/baselines.js +5 -4
- package/dist/project/file-lock.d.ts +2 -1
- package/dist/project/file-lock.js +12 -2
- package/dist/project/fs-port.d.ts +0 -1
- package/dist/project/fs-port.js +0 -1
- package/dist/project/project-store.d.ts +0 -1
- package/dist/project/project-store.js +20 -9
- package/dist/project/project-tools.d.ts +0 -1
- package/dist/project/project-tools.js +57 -9
- package/dist/project/reticle-dir.d.ts +0 -1
- package/dist/project/reticle-dir.js +0 -1
- package/dist/runs/build-verification-run.d.ts +0 -1
- package/dist/runs/build-verification-run.js +0 -1
- package/dist/runs/profile-redact.d.ts +0 -1
- package/dist/runs/profile-redact.js +0 -1
- package/dist/runs/render-report.d.ts +0 -1
- package/dist/runs/render-report.js +0 -1
- package/dist/runs/repair-prompt.d.ts +0 -1
- package/dist/runs/repair-prompt.js +0 -1
- package/dist/runs/replay-mapping.d.ts +0 -1
- package/dist/runs/replay-mapping.js +0 -1
- package/dist/runs/reticle-runner.d.ts +0 -1
- package/dist/runs/reticle-runner.js +4 -2
- package/dist/runs/risk-classify.d.ts +0 -1
- package/dist/runs/risk-classify.js +0 -1
- package/dist/runs/run-diff.d.ts +0 -1
- package/dist/runs/run-diff.js +0 -1
- package/dist/runs/run-store.d.ts +0 -1
- package/dist/runs/run-store.js +0 -1
- package/dist/runs/run-tools.d.ts +0 -1
- package/dist/runs/run-tools.js +0 -1
- package/dist/runs/runner-port.d.ts +0 -1
- package/dist/runs/runner-port.js +0 -1
- package/dist/runs/verification-sync.d.ts +23 -0
- package/dist/runs/verification-sync.js +66 -0
- package/dist/runs/verify-http.d.ts +0 -1
- package/dist/runs/verify-http.js +0 -1
- package/dist/runs/verify-server.d.ts +0 -1
- package/dist/runs/verify-server.js +0 -1
- package/dist/server-version.d.ts +0 -1
- package/dist/server-version.js +0 -1
- package/dist/session/command-timeout.d.ts +35 -0
- package/dist/session/command-timeout.js +41 -0
- package/dist/session/control-envelope.d.ts +2 -3
- package/dist/session/control-envelope.js +2 -3
- package/dist/session/last-act.d.ts +31 -0
- package/dist/session/last-act.js +24 -0
- package/dist/session/live-control-tools.d.ts +0 -1
- package/dist/session/live-control-tools.js +3 -4
- package/dist/session/live-control.d.ts +0 -1
- package/dist/session/live-control.js +0 -1
- package/dist/session/observed-state.d.ts +12 -2
- package/dist/session/observed-state.js +29 -2
- package/dist/session/output-budget.d.ts +0 -1
- package/dist/session/output-budget.js +0 -1
- package/dist/session/pending-commands.d.ts +12 -0
- package/dist/session/pending-commands.js +36 -0
- package/dist/session/presenter-args.d.ts +0 -1
- package/dist/session/presenter-args.js +0 -1
- package/dist/session/review-store.d.ts +0 -1
- package/dist/session/review-store.js +0 -1
- package/dist/session/session-health.d.ts +26 -2
- package/dist/session/session-health.js +13 -1
- package/dist/session/session-info.d.ts +29 -0
- package/dist/session/session-info.js +1 -0
- package/dist/session/session-lease.d.ts +25 -0
- package/dist/session/session-lease.js +14 -0
- package/dist/session/session-manager.d.ts +7 -19
- package/dist/session/session-manager.js +29 -2
- package/dist/session/session-readiness.d.ts +0 -1
- package/dist/session/session-readiness.js +3 -4
- package/dist/session/session-reaper.d.ts +0 -1
- package/dist/session/session-reaper.js +0 -1
- package/dist/session/session-recommendation.d.ts +0 -1
- package/dist/session/session-recommendation.js +0 -1
- package/dist/session/session-tools.d.ts +0 -1
- package/dist/session/session-tools.js +2 -3
- package/dist/session/session.d.ts +27 -53
- package/dist/session/session.js +95 -66
- package/dist/session/state-select.d.ts +0 -1
- package/dist/session/state-select.js +0 -1
- package/dist/telemetry/argument-shape.d.ts +41 -0
- package/dist/telemetry/argument-shape.js +87 -0
- package/dist/telemetry/bug-found.d.ts +33 -0
- package/dist/telemetry/bug-found.js +95 -0
- package/dist/telemetry/cli-telemetry.d.ts +13 -0
- package/dist/telemetry/cli-telemetry.js +43 -0
- package/dist/telemetry/connect-failure.d.ts +16 -0
- package/dist/telemetry/connect-failure.js +44 -0
- package/dist/telemetry/daemon-telemetry.d.ts +19 -0
- package/dist/telemetry/daemon-telemetry.js +76 -0
- package/dist/telemetry/dev-repo.d.ts +10 -0
- package/dist/telemetry/dev-repo.js +80 -0
- package/dist/telemetry/error-fingerprint.d.ts +24 -0
- package/dist/telemetry/error-fingerprint.js +88 -0
- package/dist/telemetry/feedback-cli.d.ts +31 -0
- package/dist/telemetry/feedback-cli.js +102 -0
- package/dist/telemetry/feedback-context.d.ts +32 -0
- package/dist/telemetry/feedback-context.js +143 -0
- package/dist/telemetry/feedback.d.ts +66 -0
- package/dist/telemetry/feedback.js +175 -0
- package/dist/telemetry/git-facts.d.ts +21 -0
- package/dist/telemetry/git-facts.js +109 -0
- package/dist/telemetry/identify.d.ts +22 -0
- package/dist/telemetry/identify.js +73 -0
- package/dist/telemetry/init-telemetry.d.ts +26 -0
- package/dist/telemetry/init-telemetry.js +33 -0
- package/dist/telemetry/machine-snapshot.d.ts +6 -0
- package/dist/telemetry/machine-snapshot.js +39 -0
- package/dist/telemetry/mcp-connection.d.ts +5 -0
- package/dist/telemetry/mcp-connection.js +44 -0
- package/dist/telemetry/project-profile.d.ts +44 -0
- package/dist/telemetry/project-profile.js +205 -0
- package/dist/telemetry/run-telemetry.d.ts +21 -0
- package/dist/telemetry/run-telemetry.js +62 -0
- package/dist/telemetry/session-metrics.d.ts +103 -0
- package/dist/telemetry/session-metrics.js +329 -0
- package/dist/telemetry/telemetry.d.ts +68 -0
- package/dist/telemetry/telemetry.js +313 -0
- package/dist/token-auth.d.ts +0 -1
- package/dist/token-auth.js +0 -1
- package/dist/tools/act-capsule.d.ts +23 -0
- package/dist/tools/act-capsule.js +47 -0
- package/dist/tools/act-danger.d.ts +7 -0
- package/dist/tools/act-danger.js +44 -0
- package/dist/tools/act-helpers.d.ts +17 -0
- package/dist/tools/act-helpers.js +40 -0
- package/dist/tools/act-tools.d.ts +8 -1
- package/dist/tools/act-tools.js +96 -98
- package/dist/tools/act-view.d.ts +7 -1
- package/dist/tools/act-view.js +13 -1
- package/dist/tools/assert-grade.d.ts +22 -1
- package/dist/tools/assert-grade.js +46 -1
- package/dist/tools/assert-verdict.d.ts +21 -0
- package/dist/tools/assert-verdict.js +62 -0
- package/dist/tools/browser-tools.d.ts +0 -1
- package/dist/tools/browser-tools.js +2 -2
- package/dist/tools/contract-tools.d.ts +0 -1
- package/dist/tools/contract-tools.js +0 -1
- package/dist/tools/coverage-tools.d.ts +9 -0
- package/dist/tools/coverage-tools.js +92 -0
- package/dist/tools/dynamic-tools.d.ts +0 -1
- package/dist/tools/dynamic-tools.js +21 -2
- package/dist/tools/error-recovery.d.ts +18 -2
- package/dist/tools/error-recovery.js +42 -7
- package/dist/tools/feedback-tools.d.ts +24 -0
- package/dist/tools/feedback-tools.js +180 -0
- package/dist/tools/invoke-tool.d.ts +0 -1
- package/dist/tools/invoke-tool.js +116 -2
- package/dist/tools/lease-tools.d.ts +0 -1
- package/dist/tools/lease-tools.js +6 -5
- package/dist/tools/merge-tools.d.ts +0 -1
- package/dist/tools/merge-tools.js +0 -1
- package/dist/tools/observe-tools.d.ts +0 -1
- package/dist/tools/observe-tools.js +91 -39
- package/dist/tools/profiles.d.ts +1 -2
- package/dist/tools/profiles.js +43 -16
- package/dist/tools/query-paginate.d.ts +0 -1
- package/dist/tools/query-paginate.js +0 -1
- package/dist/tools/read-tools.d.ts +0 -1
- package/dist/tools/read-tools.js +10 -8
- package/dist/tools/reconcile-tools.d.ts +2 -0
- package/dist/tools/reconcile-tools.js +111 -0
- package/dist/tools/snapshot-delta.d.ts +0 -1
- package/dist/tools/snapshot-delta.js +0 -0
- package/dist/tools/tool-invoker.d.ts +0 -1
- package/dist/tools/tool-invoker.js +0 -1
- package/dist/tools/tool-kit.d.ts +16 -1
- package/dist/tools/tool-kit.js +8 -2
- package/dist/tools/tool-names.d.ts +6 -1
- package/dist/tools/tool-names.js +6 -1
- package/dist/tools/tools-helpers.d.ts +0 -1
- package/dist/tools/tools-helpers.js +0 -1
- package/dist/tools/tools.d.ts +0 -1
- package/dist/tools/tools.js +24 -4
- package/dist/update/update-checker.d.ts +0 -1
- package/dist/update/update-checker.js +26 -2
- package/dist/update/update-nudge.d.ts +20 -0
- package/dist/update/update-nudge.js +72 -0
- package/dist/update/updater.d.ts +14 -1
- package/dist/update/updater.js +33 -2
- package/dist/visual/visual-diff.d.ts +0 -1
- package/dist/visual/visual-diff.js +0 -1
- package/dist/visual/visual-store.d.ts +0 -1
- package/dist/visual/visual-store.js +0 -1
- package/dist/visual/visual-tools.d.ts +0 -1
- package/dist/visual/visual-tools.js +103 -16
- package/package.json +2 -2
- package/dist/bridge-security.d.ts.map +0 -1
- package/dist/bridge-security.js.map +0 -1
- package/dist/bridge.d.ts.map +0 -1
- package/dist/bridge.js.map +0 -1
- package/dist/bridge.test-harness.d.ts.map +0 -1
- package/dist/bridge.test-harness.js.map +0 -1
- package/dist/capsule/capsule-store.d.ts.map +0 -1
- package/dist/capsule/capsule-store.js.map +0 -1
- package/dist/capsule/capsule.d.ts.map +0 -1
- package/dist/capsule/capsule.js.map +0 -1
- package/dist/capsule/causal-summary.d.ts.map +0 -1
- package/dist/capsule/causal-summary.js.map +0 -1
- package/dist/capsule/divergence.d.ts.map +0 -1
- package/dist/capsule/divergence.js.map +0 -1
- package/dist/capsule/minimize.d.ts.map +0 -1
- package/dist/capsule/minimize.js.map +0 -1
- package/dist/capsule/predicate-to-links.d.ts.map +0 -1
- package/dist/capsule/predicate-to-links.js.map +0 -1
- package/dist/cli-flow-commands.d.ts.map +0 -1
- package/dist/cli-flow-commands.js.map +0 -1
- package/dist/cli-launch.d.ts.map +0 -1
- package/dist/cli-launch.js.map +0 -1
- package/dist/cli-parse.d.ts.map +0 -1
- package/dist/cli-parse.js.map +0 -1
- package/dist/cli-port.d.ts.map +0 -1
- package/dist/cli-port.js.map +0 -1
- package/dist/cli-verify.d.ts.map +0 -1
- package/dist/cli-verify.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cloud/cloud-sync.d.ts.map +0 -1
- package/dist/cloud/cloud-sync.js.map +0 -1
- package/dist/crawl/crawl-tools.d.ts.map +0 -1
- package/dist/crawl/crawl-tools.js.map +0 -1
- package/dist/crawl/crawl.d.ts.map +0 -1
- package/dist/crawl/crawl.js.map +0 -1
- package/dist/daemon-resilience.d.ts.map +0 -1
- package/dist/daemon-resilience.js.map +0 -1
- package/dist/daemon.d.ts.map +0 -1
- package/dist/daemon.js.map +0 -1
- package/dist/domain/domain-model.d.ts.map +0 -1
- package/dist/domain/domain-model.js.map +0 -1
- package/dist/domain/domain-tools.d.ts.map +0 -1
- package/dist/domain/domain-tools.js.map +0 -1
- package/dist/domain/flow-risk.d.ts.map +0 -1
- package/dist/domain/flow-risk.js.map +0 -1
- package/dist/ee/audit-log.d.ts.map +0 -1
- package/dist/ee/audit-log.js.map +0 -1
- package/dist/events/event-filters.d.ts.map +0 -1
- package/dist/events/event-filters.js.map +0 -1
- package/dist/events/predicate-eval.d.ts.map +0 -1
- package/dist/events/predicate-eval.js.map +0 -1
- package/dist/events/predicate.d.ts.map +0 -1
- package/dist/events/predicate.js.map +0 -1
- package/dist/events/reaction.d.ts.map +0 -1
- package/dist/events/reaction.js.map +0 -1
- package/dist/events/ring-buffer.d.ts.map +0 -1
- package/dist/events/ring-buffer.js.map +0 -1
- package/dist/flows/affected.d.ts.map +0 -1
- package/dist/flows/affected.js.map +0 -1
- package/dist/flows/annotate-tools.d.ts.map +0 -1
- package/dist/flows/annotate-tools.js.map +0 -1
- package/dist/flows/annotate.d.ts.map +0 -1
- package/dist/flows/annotate.js.map +0 -1
- package/dist/flows/annotation-store.d.ts.map +0 -1
- package/dist/flows/annotation-store.js.map +0 -1
- package/dist/flows/assertion-integrity.d.ts.map +0 -1
- package/dist/flows/assertion-integrity.js.map +0 -1
- package/dist/flows/assertion-tiers-store.d.ts.map +0 -1
- package/dist/flows/assertion-tiers-store.js.map +0 -1
- package/dist/flows/buddy-status.d.ts.map +0 -1
- package/dist/flows/buddy-status.js.map +0 -1
- package/dist/flows/coverage.d.ts.map +0 -1
- package/dist/flows/coverage.js.map +0 -1
- package/dist/flows/decision.d.ts.map +0 -1
- package/dist/flows/decision.js.map +0 -1
- package/dist/flows/flake-store.d.ts.map +0 -1
- package/dist/flows/flake-store.js.map +0 -1
- package/dist/flows/flake.d.ts.map +0 -1
- package/dist/flows/flake.js.map +0 -1
- package/dist/flows/flow-classify.d.ts.map +0 -1
- package/dist/flows/flow-classify.js.map +0 -1
- package/dist/flows/flow-replay-run.d.ts.map +0 -1
- package/dist/flows/flow-replay-run.js.map +0 -1
- package/dist/flows/flow-replay.d.ts.map +0 -1
- package/dist/flows/flow-replay.js.map +0 -1
- package/dist/flows/flow-report.d.ts.map +0 -1
- package/dist/flows/flow-report.js.map +0 -1
- package/dist/flows/flow-scope.d.ts.map +0 -1
- package/dist/flows/flow-scope.js.map +0 -1
- package/dist/flows/flow-sources.d.ts.map +0 -1
- package/dist/flows/flow-sources.js.map +0 -1
- package/dist/flows/flow-success.d.ts.map +0 -1
- package/dist/flows/flow-success.js.map +0 -1
- package/dist/flows/flow-tools.d.ts.map +0 -1
- package/dist/flows/flow-tools.js.map +0 -1
- package/dist/flows/flows.d.ts.map +0 -1
- package/dist/flows/flows.js.map +0 -1
- package/dist/flows/gate.d.ts.map +0 -1
- package/dist/flows/gate.js.map +0 -1
- package/dist/flows/git-changed.d.ts.map +0 -1
- package/dist/flows/git-changed.js.map +0 -1
- package/dist/flows/heal-run.d.ts.map +0 -1
- package/dist/flows/heal-run.js.map +0 -1
- package/dist/flows/heal.d.ts.map +0 -1
- package/dist/flows/heal.js.map +0 -1
- package/dist/flows/parallel-suite.d.ts.map +0 -1
- package/dist/flows/parallel-suite.js.map +0 -1
- package/dist/flows/recordings.d.ts.map +0 -1
- package/dist/flows/recordings.js.map +0 -1
- package/dist/flows/replay.d.ts.map +0 -1
- package/dist/flows/replay.js.map +0 -1
- package/dist/flows/watch-batcher.d.ts.map +0 -1
- package/dist/flows/watch-batcher.js.map +0 -1
- package/dist/honesty/blind-spots.d.ts.map +0 -1
- package/dist/honesty/blind-spots.js.map +0 -1
- package/dist/honesty/honesty.d.ts.map +0 -1
- package/dist/honesty/honesty.js.map +0 -1
- package/dist/http-server.d.ts.map +0 -1
- package/dist/http-server.js.map +0 -1
- package/dist/idle-shutdown.d.ts.map +0 -1
- package/dist/idle-shutdown.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/init/agent-rules.d.ts.map +0 -1
- package/dist/init/agent-rules.js.map +0 -1
- package/dist/init/cursor.d.ts.map +0 -1
- package/dist/init/cursor.js.map +0 -1
- package/dist/init/detect.d.ts.map +0 -1
- package/dist/init/detect.js.map +0 -1
- package/dist/init/mcp.d.ts.map +0 -1
- package/dist/init/mcp.js.map +0 -1
- package/dist/init/node-io.d.ts.map +0 -1
- package/dist/init/node-io.js.map +0 -1
- package/dist/init/plan.d.ts.map +0 -1
- package/dist/init/plan.js.map +0 -1
- package/dist/init/project-id.d.ts.map +0 -1
- package/dist/init/project-id.js.map +0 -1
- package/dist/init/run.d.ts.map +0 -1
- package/dist/init/run.js.map +0 -1
- package/dist/init/snippets.d.ts.map +0 -1
- package/dist/init/snippets.js.map +0 -1
- package/dist/init/vite-config.d.ts.map +0 -1
- package/dist/init/vite-config.js.map +0 -1
- package/dist/input/network-detail.d.ts.map +0 -1
- package/dist/input/network-detail.js.map +0 -1
- package/dist/input/network-mock-tools.d.ts.map +0 -1
- package/dist/input/network-mock-tools.js.map +0 -1
- package/dist/input/network-mock.d.ts.map +0 -1
- package/dist/input/network-mock.js.map +0 -1
- package/dist/input/real-input.d.ts.map +0 -1
- package/dist/input/real-input.js.map +0 -1
- package/dist/input/scroll-find.d.ts.map +0 -1
- package/dist/input/scroll-find.js.map +0 -1
- package/dist/input/scroll-tools.d.ts.map +0 -1
- package/dist/input/scroll-tools.js.map +0 -1
- package/dist/input/viewport-tools.d.ts.map +0 -1
- package/dist/input/viewport-tools.js.map +0 -1
- package/dist/journal/ambient-store.d.ts.map +0 -1
- package/dist/journal/ambient-store.js.map +0 -1
- package/dist/journal/ambient.d.ts.map +0 -1
- package/dist/journal/ambient.js.map +0 -1
- package/dist/journal/attach-journal.d.ts.map +0 -1
- package/dist/journal/attach-journal.js.map +0 -1
- package/dist/journal/deviation-report.d.ts.map +0 -1
- package/dist/journal/deviation-report.js.map +0 -1
- package/dist/journal/deviation-service.d.ts.map +0 -1
- package/dist/journal/deviation-service.js.map +0 -1
- package/dist/journal/envelope-store.d.ts.map +0 -1
- package/dist/journal/envelope-store.js.map +0 -1
- package/dist/journal/envelope.d.ts.map +0 -1
- package/dist/journal/envelope.js.map +0 -1
- package/dist/journal/journal-query.d.ts.map +0 -1
- package/dist/journal/journal-query.js.map +0 -1
- package/dist/journal/journal-recorder.d.ts.map +0 -1
- package/dist/journal/journal-recorder.js.map +0 -1
- package/dist/journal/retention.d.ts.map +0 -1
- package/dist/journal/retention.js.map +0 -1
- package/dist/journal/rollups.d.ts.map +0 -1
- package/dist/journal/rollups.js.map +0 -1
- package/dist/journal/session-end.d.ts.map +0 -1
- package/dist/journal/session-end.js.map +0 -1
- package/dist/journal/session-journal.d.ts.map +0 -1
- package/dist/journal/session-journal.js.map +0 -1
- package/dist/license/license.d.ts.map +0 -1
- package/dist/license/license.js.map +0 -1
- package/dist/log.d.ts.map +0 -1
- package/dist/log.js.map +0 -1
- package/dist/mcp-proxy.d.ts.map +0 -1
- package/dist/mcp-proxy.js.map +0 -1
- package/dist/mcp.d.ts.map +0 -1
- package/dist/mcp.js.map +0 -1
- package/dist/oracles/flow-instrument-gaps.d.ts.map +0 -1
- package/dist/oracles/flow-instrument-gaps.js.map +0 -1
- package/dist/oracles/propose-consequences.d.ts.map +0 -1
- package/dist/oracles/propose-consequences.js.map +0 -1
- package/dist/oracles/self-instrument.d.ts.map +0 -1
- package/dist/oracles/self-instrument.js.map +0 -1
- package/dist/pairing-token.d.ts.map +0 -1
- package/dist/pairing-token.js.map +0 -1
- package/dist/phenomena/phenomena.d.ts.map +0 -1
- package/dist/phenomena/phenomena.js.map +0 -1
- package/dist/pool/browser-pool.d.ts.map +0 -1
- package/dist/pool/browser-pool.js.map +0 -1
- package/dist/pool/lease-reaper.d.ts.map +0 -1
- package/dist/pool/lease-reaper.js.map +0 -1
- package/dist/pool/playwright-launcher.d.ts.map +0 -1
- package/dist/pool/playwright-launcher.js.map +0 -1
- package/dist/project/baselines.d.ts.map +0 -1
- package/dist/project/baselines.js.map +0 -1
- package/dist/project/file-lock.d.ts.map +0 -1
- package/dist/project/file-lock.js.map +0 -1
- package/dist/project/fs-port.d.ts.map +0 -1
- package/dist/project/fs-port.js.map +0 -1
- package/dist/project/project-store.d.ts.map +0 -1
- package/dist/project/project-store.js.map +0 -1
- package/dist/project/project-tools.d.ts.map +0 -1
- package/dist/project/project-tools.js.map +0 -1
- package/dist/project/reticle-dir.d.ts.map +0 -1
- package/dist/project/reticle-dir.js.map +0 -1
- package/dist/runs/build-verification-run.d.ts.map +0 -1
- package/dist/runs/build-verification-run.js.map +0 -1
- package/dist/runs/profile-redact.d.ts.map +0 -1
- package/dist/runs/profile-redact.js.map +0 -1
- package/dist/runs/render-report.d.ts.map +0 -1
- package/dist/runs/render-report.js.map +0 -1
- package/dist/runs/repair-prompt.d.ts.map +0 -1
- package/dist/runs/repair-prompt.js.map +0 -1
- package/dist/runs/replay-mapping.d.ts.map +0 -1
- package/dist/runs/replay-mapping.js.map +0 -1
- package/dist/runs/reticle-runner.d.ts.map +0 -1
- package/dist/runs/reticle-runner.js.map +0 -1
- package/dist/runs/risk-classify.d.ts.map +0 -1
- package/dist/runs/risk-classify.js.map +0 -1
- package/dist/runs/run-diff.d.ts.map +0 -1
- package/dist/runs/run-diff.js.map +0 -1
- package/dist/runs/run-store.d.ts.map +0 -1
- package/dist/runs/run-store.js.map +0 -1
- package/dist/runs/run-tools.d.ts.map +0 -1
- package/dist/runs/run-tools.js.map +0 -1
- package/dist/runs/runner-port.d.ts.map +0 -1
- package/dist/runs/runner-port.js.map +0 -1
- package/dist/runs/verify-http.d.ts.map +0 -1
- package/dist/runs/verify-http.js.map +0 -1
- package/dist/runs/verify-server.d.ts.map +0 -1
- package/dist/runs/verify-server.js.map +0 -1
- package/dist/server-version.d.ts.map +0 -1
- package/dist/server-version.js.map +0 -1
- package/dist/session/control-envelope.d.ts.map +0 -1
- package/dist/session/control-envelope.js.map +0 -1
- package/dist/session/live-control-tools.d.ts.map +0 -1
- package/dist/session/live-control-tools.js.map +0 -1
- package/dist/session/live-control.d.ts.map +0 -1
- package/dist/session/live-control.js.map +0 -1
- package/dist/session/observed-state.d.ts.map +0 -1
- package/dist/session/observed-state.js.map +0 -1
- package/dist/session/output-budget.d.ts.map +0 -1
- package/dist/session/output-budget.js.map +0 -1
- package/dist/session/presenter-args.d.ts.map +0 -1
- package/dist/session/presenter-args.js.map +0 -1
- package/dist/session/review-store.d.ts.map +0 -1
- package/dist/session/review-store.js.map +0 -1
- package/dist/session/session-health.d.ts.map +0 -1
- package/dist/session/session-health.js.map +0 -1
- package/dist/session/session-manager.d.ts.map +0 -1
- package/dist/session/session-manager.js.map +0 -1
- package/dist/session/session-readiness.d.ts.map +0 -1
- package/dist/session/session-readiness.js.map +0 -1
- package/dist/session/session-reaper.d.ts.map +0 -1
- package/dist/session/session-reaper.js.map +0 -1
- package/dist/session/session-recommendation.d.ts.map +0 -1
- package/dist/session/session-recommendation.js.map +0 -1
- package/dist/session/session-tools.d.ts.map +0 -1
- package/dist/session/session-tools.js.map +0 -1
- package/dist/session/session.d.ts.map +0 -1
- package/dist/session/session.js.map +0 -1
- package/dist/session/state-select.d.ts.map +0 -1
- package/dist/session/state-select.js.map +0 -1
- package/dist/token-auth.d.ts.map +0 -1
- package/dist/token-auth.js.map +0 -1
- package/dist/tools/act-tools.d.ts.map +0 -1
- package/dist/tools/act-tools.js.map +0 -1
- package/dist/tools/act-view.d.ts.map +0 -1
- package/dist/tools/act-view.js.map +0 -1
- package/dist/tools/assert-grade.d.ts.map +0 -1
- package/dist/tools/assert-grade.js.map +0 -1
- package/dist/tools/browser-tools.d.ts.map +0 -1
- package/dist/tools/browser-tools.js.map +0 -1
- package/dist/tools/contract-tools.d.ts.map +0 -1
- package/dist/tools/contract-tools.js.map +0 -1
- package/dist/tools/dynamic-tools.d.ts.map +0 -1
- package/dist/tools/dynamic-tools.js.map +0 -1
- package/dist/tools/error-recovery.d.ts.map +0 -1
- package/dist/tools/error-recovery.js.map +0 -1
- package/dist/tools/invoke-tool.d.ts.map +0 -1
- package/dist/tools/invoke-tool.js.map +0 -1
- package/dist/tools/lease-tools.d.ts.map +0 -1
- package/dist/tools/lease-tools.js.map +0 -1
- package/dist/tools/merge-tools.d.ts.map +0 -1
- package/dist/tools/merge-tools.js.map +0 -1
- package/dist/tools/observe-tools.d.ts.map +0 -1
- package/dist/tools/observe-tools.js.map +0 -1
- package/dist/tools/profiles.d.ts.map +0 -1
- package/dist/tools/profiles.js.map +0 -1
- package/dist/tools/query-paginate.d.ts.map +0 -1
- package/dist/tools/query-paginate.js.map +0 -1
- package/dist/tools/read-tools.d.ts.map +0 -1
- package/dist/tools/read-tools.js.map +0 -1
- package/dist/tools/snapshot-delta.d.ts.map +0 -1
- package/dist/tools/snapshot-delta.js.map +0 -1
- package/dist/tools/tool-invoker.d.ts.map +0 -1
- package/dist/tools/tool-invoker.js.map +0 -1
- package/dist/tools/tool-kit.d.ts.map +0 -1
- package/dist/tools/tool-kit.js.map +0 -1
- package/dist/tools/tool-names.d.ts.map +0 -1
- package/dist/tools/tool-names.js.map +0 -1
- package/dist/tools/tools-helpers.d.ts.map +0 -1
- package/dist/tools/tools-helpers.js.map +0 -1
- package/dist/tools/tools.d.ts.map +0 -1
- package/dist/tools/tools.js.map +0 -1
- package/dist/update/update-checker.d.ts.map +0 -1
- package/dist/update/update-checker.js.map +0 -1
- package/dist/update/updater.d.ts.map +0 -1
- package/dist/update/updater.js.map +0 -1
- package/dist/visual/visual-diff.d.ts.map +0 -1
- package/dist/visual/visual-diff.js.map +0 -1
- package/dist/visual/visual-store.d.ts.map +0 -1
- package/dist/visual/visual-store.js.map +0 -1
- package/dist/visual/visual-tools.d.ts.map +0 -1
- package/dist/visual/visual-tools.js.map +0 -1
|
@@ -18,6 +18,7 @@ export const PredicateSchema = z.lazy(() => z.discriminatedUnion('kind', [
|
|
|
18
18
|
method: z.string().optional(),
|
|
19
19
|
urlContains: z.string().optional(),
|
|
20
20
|
status: z.number().optional(),
|
|
21
|
+
ok: z.boolean().optional(),
|
|
21
22
|
since: z.number().optional(),
|
|
22
23
|
count: z.number().int().nonnegative().optional(),
|
|
23
24
|
}),
|
|
@@ -129,6 +130,21 @@ function dataMatches(actual, pattern) {
|
|
|
129
130
|
}
|
|
130
131
|
return true;
|
|
131
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Did this call succeed, as the app experienced it?
|
|
135
|
+
*
|
|
136
|
+
* `ok` is authoritative when present — IPC sets it explicitly, because an IPC call has no status
|
|
137
|
+
* code and the 200/500 Reticle derives is a convenience, not a fact. Falling back to the HTTP status
|
|
138
|
+
* keeps ordinary web requests working without the observer having to set the field everywhere.
|
|
139
|
+
*
|
|
140
|
+
* This exists so an agent can assert on the OUTCOME rather than on a number Reticle invented.
|
|
141
|
+
*/
|
|
142
|
+
function callSucceeded(data) {
|
|
143
|
+
if (typeof data['ok'] === 'boolean')
|
|
144
|
+
return data['ok'];
|
|
145
|
+
const status = num(data['status']);
|
|
146
|
+
return status === undefined || status < 400;
|
|
147
|
+
}
|
|
132
148
|
export function evalNet(events, p) {
|
|
133
149
|
const since = p.since ?? 0;
|
|
134
150
|
const matches = events.filter((e) => {
|
|
@@ -143,6 +159,8 @@ export function evalNet(events, p) {
|
|
|
143
159
|
}
|
|
144
160
|
if (p.status !== undefined && num(d['status']) !== p.status)
|
|
145
161
|
return false;
|
|
162
|
+
if (p.ok !== undefined && callSucceeded(d) !== p.ok)
|
|
163
|
+
return false;
|
|
146
164
|
return true;
|
|
147
165
|
});
|
|
148
166
|
// `count` (exact) turns presence into a cardinality assertion — catches the double-submit /
|
|
@@ -192,11 +210,17 @@ export function evalRoute(events, p) {
|
|
|
192
210
|
assertion: 'route.pathname',
|
|
193
211
|
};
|
|
194
212
|
}
|
|
195
|
-
|
|
213
|
+
// `contains` matches the WHOLE route — path + query + fragment — while `pathname` above stays an
|
|
214
|
+
// exact path match. A hash router keeps the entire route in the fragment, so matching pathname
|
|
215
|
+
// alone made `contains` unsatisfiable for every HashRouter app; that is the standard router for a
|
|
216
|
+
// packaged Electron/Tauri renderer, where an absolute pushState would rewrite the URL to a
|
|
217
|
+
// nonexistent file.
|
|
218
|
+
const fullRoute = `${pathname}${str(last.data['search']) ?? ''}${str(last.data['hash']) ?? ''}`;
|
|
219
|
+
if (p.contains !== undefined && !fullRoute.includes(p.contains)) {
|
|
196
220
|
return {
|
|
197
221
|
pass: false,
|
|
198
|
-
failureReason: `route '${
|
|
199
|
-
observed: `route '${
|
|
222
|
+
failureReason: `route '${fullRoute}' does not contain '${p.contains}'`,
|
|
223
|
+
observed: `route '${fullRoute}'`,
|
|
200
224
|
expected: `a route containing '${p.contains}'`,
|
|
201
225
|
assertion: 'route.contains',
|
|
202
226
|
};
|
|
@@ -380,14 +404,38 @@ export function evalSettled(events, p, now) {
|
|
|
380
404
|
for (const [id, pending] of pendingById) {
|
|
381
405
|
inFlight += Math.max(0, pending - (doneById.get(id) ?? 0));
|
|
382
406
|
}
|
|
383
|
-
|
|
407
|
+
// A completed request whose BODY is still streaming is not a finished request. `fetch` resolves at
|
|
408
|
+
// HEADERS, so on a Next.js App Router page the RSC payload reported complete 16 ms in while the
|
|
409
|
+
// Suspense boundary's content arrived 889 ms later — and the silence between them read as a settled
|
|
410
|
+
// page with a loading spinner still on screen. An OPEN with no CLOSE is in flight, counted the same
|
|
411
|
+
// way as a pending with no completion.
|
|
412
|
+
const openStreams = new Set();
|
|
413
|
+
for (const e of events) {
|
|
414
|
+
if (e.type !== EventType.NET_STREAM)
|
|
415
|
+
continue;
|
|
416
|
+
const id = str(e.data['id']);
|
|
417
|
+
if (id === undefined)
|
|
418
|
+
continue;
|
|
419
|
+
const direction = str(e.data['direction']);
|
|
420
|
+
if (direction === 'open')
|
|
421
|
+
openStreams.add(id);
|
|
422
|
+
else if (direction === 'close')
|
|
423
|
+
openStreams.delete(id);
|
|
424
|
+
}
|
|
425
|
+
const streaming = openStreams.size;
|
|
426
|
+
if (inFlight + streaming > 0) {
|
|
427
|
+
const what = streaming === 0
|
|
428
|
+
? `${String(inFlight)} request(s) still in flight`
|
|
429
|
+
: inFlight === 0
|
|
430
|
+
? `${String(streaming)} response body(ies) still streaming`
|
|
431
|
+
: `${String(inFlight)} request(s) in flight and ${String(streaming)} response body(ies) still streaming`;
|
|
384
432
|
return {
|
|
385
433
|
pass: false,
|
|
386
|
-
failureReason: `not settled: ${
|
|
387
|
-
observed:
|
|
388
|
-
expected: 'no requests in flight',
|
|
434
|
+
failureReason: `not settled: ${what}`,
|
|
435
|
+
observed: what,
|
|
436
|
+
expected: 'no requests in flight and no response bodies still streaming',
|
|
389
437
|
assertion: 'settled.in-flight',
|
|
390
|
-
evidence: { settled: false, inFlight },
|
|
438
|
+
evidence: { settled: false, inFlight, ...(streaming > 0 ? { streaming } : {}) },
|
|
391
439
|
};
|
|
392
440
|
}
|
|
393
441
|
let lastT = -1;
|
|
@@ -417,4 +465,3 @@ export function evalSettled(events, p, now) {
|
|
|
417
465
|
evidence: { quietForMs, lastActivity: lastType },
|
|
418
466
|
};
|
|
419
467
|
}
|
|
420
|
-
//# sourceMappingURL=predicate-eval.js.map
|
|
@@ -37,4 +37,3 @@ export declare function waitForPredicate(session: PredicateSession, predicate: P
|
|
|
37
37
|
* green ⇏ this-branch-held, re-checks each branch and keeps the winners.
|
|
38
38
|
*/
|
|
39
39
|
export declare function provenExpectedLinks(session: PredicateSession, predicate: Predicate, since?: number): Promise<ExpectedLink[]>;
|
|
40
|
-
//# sourceMappingURL=predicate.d.ts.map
|
package/dist/events/predicate.js
CHANGED
|
@@ -219,6 +219,38 @@ const POLL_INTERVAL_MS = 150;
|
|
|
219
219
|
* DOM/STATE round-trips. Small enough that added pass-detection latency is negligible next to the
|
|
220
220
|
* poll cadence, large enough to collapse a per-frame event storm into a bounded recheck rate. */
|
|
221
221
|
const MIN_RECHECK_GAP_MS = 25;
|
|
222
|
+
/**
|
|
223
|
+
* How long an exact-count predicate keeps watching AFTER it first reads true.
|
|
224
|
+
*
|
|
225
|
+
* A count only rises while a window is open, so "exactly N" is a statement about the END of one and
|
|
226
|
+
* cannot be settled early — yet every wait here resolves the moment a check passes. Live, on a real
|
|
227
|
+
* payments dashboard: a Refund confirm fired TWO POSTs 59 ms apart, and
|
|
228
|
+
* `until: { kind:'net', method:'POST', urlContains:'/refund', count:1 }` returned
|
|
229
|
+
* `pass: true, matched: 1`. Not a counting bug — `evalNet` counts occurrences correctly. The wait had
|
|
230
|
+
* already stopped looking. So `count: 1` silently meant "at least 1", which is the assertion the
|
|
231
|
+
* caller wrote `count` specifically to avoid, and the branch implementing it claims to catch "the
|
|
232
|
+
* double-submit / useEffect-double-fire / retry-storm regression class".
|
|
233
|
+
*
|
|
234
|
+
* 300 ms is chosen against the measured defect: the observed double-submit gap was 59 ms, a React
|
|
235
|
+
* double-effect fires within one commit, and a retry storm is faster still. It is a real ceiling, not
|
|
236
|
+
* a proof — a duplicate arriving 400 ms later still passes. Widening it costs every exact-count
|
|
237
|
+
* assertion that latency, so this trades an unbounded false green for a bounded one and says so.
|
|
238
|
+
*/
|
|
239
|
+
const COUNT_CONFIRM_MS = 300;
|
|
240
|
+
/**
|
|
241
|
+
* Does this predicate assert an exact cardinality anywhere inside it?
|
|
242
|
+
*
|
|
243
|
+
* Only these hold after passing. A presence-only predicate ("at least one") IS satisfiable early and
|
|
244
|
+
* must stay that way, or every ordinary wait pays the confirmation delay for nothing.
|
|
245
|
+
*/
|
|
246
|
+
function assertsExactCount(predicate) {
|
|
247
|
+
if (predicate.kind === 'allOf' || predicate.kind === 'anyOf') {
|
|
248
|
+
return predicate.predicates.some(assertsExactCount);
|
|
249
|
+
}
|
|
250
|
+
if (predicate.kind === 'not')
|
|
251
|
+
return assertsExactCount(predicate.predicate);
|
|
252
|
+
return predicate.kind === 'net' && predicate.count !== undefined;
|
|
253
|
+
}
|
|
222
254
|
/**
|
|
223
255
|
* Evaluate now, else wait for it to become true (on each event + a poll) until timeout. `since` is
|
|
224
256
|
* the event-time floor (see evaluatePredicate) so a waiter cannot resolve on a stale buffered event.
|
|
@@ -231,6 +263,10 @@ export function waitForPredicate(session, predicate, timeoutMs, since = 0) {
|
|
|
231
263
|
failureReason: error instanceof Error ? error.message : String(error),
|
|
232
264
|
});
|
|
233
265
|
let cooldownTimer;
|
|
266
|
+
// An exact-count wait keeps watching after it first reads true — see COUNT_CONFIRM_MS.
|
|
267
|
+
const holdsForCount = assertsExactCount(predicate);
|
|
268
|
+
let confirming = false;
|
|
269
|
+
let confirmTimer;
|
|
234
270
|
const finish = (result) => {
|
|
235
271
|
if (done)
|
|
236
272
|
return;
|
|
@@ -240,6 +276,8 @@ export function waitForPredicate(session, predicate, timeoutMs, since = 0) {
|
|
|
240
276
|
clearTimeout(timer);
|
|
241
277
|
if (cooldownTimer !== undefined)
|
|
242
278
|
clearTimeout(cooldownTimer);
|
|
279
|
+
if (confirmTimer !== undefined)
|
|
280
|
+
clearTimeout(confirmTimer);
|
|
243
281
|
resolve(result);
|
|
244
282
|
};
|
|
245
283
|
// Coalesce re-checks: at most ONE evaluatePredicate is ever in flight (each can be a browser
|
|
@@ -267,8 +305,25 @@ export function waitForPredicate(session, predicate, timeoutMs, since = 0) {
|
|
|
267
305
|
// final timeout eval below runs with full diagnostics.
|
|
268
306
|
void evaluatePredicate(session, predicate, since, false)
|
|
269
307
|
.then((r) => {
|
|
270
|
-
if (r.pass)
|
|
308
|
+
if (!r.pass)
|
|
309
|
+
return;
|
|
310
|
+
// "Exactly N" cannot be concluded from a passing sample — the count can still rise. Hold,
|
|
311
|
+
// re-evaluate WITH diagnostics, and let that second read be the verdict: if an N+1th
|
|
312
|
+
// arrived in the meantime it now fails, carrying observed/expected rather than a bare no.
|
|
313
|
+
if (!holdsForCount) {
|
|
271
314
|
finish(r);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (confirming)
|
|
318
|
+
return;
|
|
319
|
+
confirming = true;
|
|
320
|
+
confirmTimer = setTimeout(() => {
|
|
321
|
+
void evaluatePredicate(session, predicate, since)
|
|
322
|
+
.then(finish)
|
|
323
|
+
.catch((error) => {
|
|
324
|
+
finish(failed(error));
|
|
325
|
+
});
|
|
326
|
+
}, COUNT_CONFIRM_MS);
|
|
272
327
|
})
|
|
273
328
|
.catch((error) => {
|
|
274
329
|
finish(failed(error));
|
|
@@ -339,4 +394,3 @@ export async function provenExpectedLinks(session, predicate, since = 0) {
|
|
|
339
394
|
}
|
|
340
395
|
return predicateToExpectedLinks(predicate);
|
|
341
396
|
}
|
|
342
|
-
//# sourceMappingURL=predicate.js.map
|
package/dist/events/reaction.js
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare what the API SAID against what the page SHOWS.
|
|
3
|
+
*
|
|
4
|
+
* Every other check in Reticle reads one side. This reads both, which is the one comparison only
|
|
5
|
+
* something living inside the app can make: the response body and the rendered text are in the same
|
|
6
|
+
* process, and nothing else holds them at the same time. A screenshot has the render and not the
|
|
7
|
+
* data; a network log has the data and not the render; a human reading either one sees a number that
|
|
8
|
+
* looks right.
|
|
9
|
+
*
|
|
10
|
+
* Two findings, both measured on a real payments dashboard:
|
|
11
|
+
*
|
|
12
|
+
* - **the wrong currency.** `GET /payments` returns `{amount: 7997, currency: "USD"}` and the row
|
|
13
|
+
* renders "₹79.97". The number is correct, the unit is not — a dollar amount presented as rupees.
|
|
14
|
+
* Every channel agrees: 200, the digits match the data, the page settles.
|
|
15
|
+
* - **a status that is never shown.** `GET /settlements` returns `status: "on_hold"` with
|
|
16
|
+
* `hold_reason: "kyc_documents_expired"`, and the badge reads "pending". A merchant reads
|
|
17
|
+
* "pending" and waits, while the payout is blocked on expired documents.
|
|
18
|
+
*
|
|
19
|
+
* Pure: response bodies and page text in, findings out. No session, no DOM, no clock.
|
|
20
|
+
*/
|
|
21
|
+
/** A finding: one field of one entity whose rendering disagrees with the data. */
|
|
22
|
+
export interface Mismatch {
|
|
23
|
+
entity: string;
|
|
24
|
+
field: string;
|
|
25
|
+
/** What the API returned. */
|
|
26
|
+
api: string;
|
|
27
|
+
/** What the page shows instead — absent when the value simply never appears. */
|
|
28
|
+
rendered?: string;
|
|
29
|
+
why: string;
|
|
30
|
+
}
|
|
31
|
+
/** Every object carrying an `id`, at any depth. These are the things a UI renders as rows. */
|
|
32
|
+
export declare function entitiesIn(node: unknown, out?: Record<string, unknown>[]): Record<string, unknown>[];
|
|
33
|
+
/**
|
|
34
|
+
* Compare entities against the page's rendered text.
|
|
35
|
+
*
|
|
36
|
+
* `pageText` is the visible text of the page — the snapshot's own tree is exactly this, so no new
|
|
37
|
+
* capability is needed to obtain it.
|
|
38
|
+
*/
|
|
39
|
+
export declare function reconcile(bodies: readonly unknown[], pageText: string): Mismatch[];
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare what the API SAID against what the page SHOWS.
|
|
3
|
+
*
|
|
4
|
+
* Every other check in Reticle reads one side. This reads both, which is the one comparison only
|
|
5
|
+
* something living inside the app can make: the response body and the rendered text are in the same
|
|
6
|
+
* process, and nothing else holds them at the same time. A screenshot has the render and not the
|
|
7
|
+
* data; a network log has the data and not the render; a human reading either one sees a number that
|
|
8
|
+
* looks right.
|
|
9
|
+
*
|
|
10
|
+
* Two findings, both measured on a real payments dashboard:
|
|
11
|
+
*
|
|
12
|
+
* - **the wrong currency.** `GET /payments` returns `{amount: 7997, currency: "USD"}` and the row
|
|
13
|
+
* renders "₹79.97". The number is correct, the unit is not — a dollar amount presented as rupees.
|
|
14
|
+
* Every channel agrees: 200, the digits match the data, the page settles.
|
|
15
|
+
* - **a status that is never shown.** `GET /settlements` returns `status: "on_hold"` with
|
|
16
|
+
* `hold_reason: "kyc_documents_expired"`, and the badge reads "pending". A merchant reads
|
|
17
|
+
* "pending" and waits, while the payout is blocked on expired documents.
|
|
18
|
+
*
|
|
19
|
+
* Pure: response bodies and page text in, findings out. No session, no DOM, no clock.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Currency markers, symbol and code. Deliberately a closed list: currency is one of the few things an
|
|
23
|
+
* app renders that has a fixed, publicly agreed vocabulary, which is what makes this checkable
|
|
24
|
+
* without knowing anything about the app.
|
|
25
|
+
*/
|
|
26
|
+
const CURRENCY_MARKS = {
|
|
27
|
+
INR: ['₹', 'INR', 'Rs.', 'Rs '],
|
|
28
|
+
USD: ['$', 'USD', 'US$'],
|
|
29
|
+
EUR: ['€', 'EUR'],
|
|
30
|
+
GBP: ['£', 'GBP'],
|
|
31
|
+
JPY: ['¥', 'JPY'],
|
|
32
|
+
AED: ['AED', 'د.إ'],
|
|
33
|
+
SGD: ['SGD', 'S$'],
|
|
34
|
+
AUD: ['AUD', 'A$'],
|
|
35
|
+
};
|
|
36
|
+
/** Currencies whose minor unit is not 1/100. Rendering these needs a different divisor. */
|
|
37
|
+
const ZERO_DECIMAL = new Set(['JPY', 'KRW', 'VND']);
|
|
38
|
+
/** Fields that name a state — the ones a badge renders and a user acts on. */
|
|
39
|
+
const STATE_FIELDS = new Set(['status', 'state']);
|
|
40
|
+
const ITEM_KEYS = ['items', 'results', 'data', 'records', 'rows', 'entries'];
|
|
41
|
+
function isRecord(value) {
|
|
42
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
43
|
+
}
|
|
44
|
+
/** Every object carrying an `id`, at any depth. These are the things a UI renders as rows. */
|
|
45
|
+
export function entitiesIn(node, out = []) {
|
|
46
|
+
if (Array.isArray(node)) {
|
|
47
|
+
for (const item of node)
|
|
48
|
+
entitiesIn(item, out);
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
if (!isRecord(node))
|
|
52
|
+
return out;
|
|
53
|
+
if (node['id'] !== undefined)
|
|
54
|
+
out.push(node);
|
|
55
|
+
for (const key of ITEM_KEYS) {
|
|
56
|
+
if (node[key] !== undefined)
|
|
57
|
+
entitiesIn(node[key], out);
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Every way a UI might print a minor-unit integer as a major-unit amount.
|
|
63
|
+
*
|
|
64
|
+
* Real interfaces group digits, and they do not agree on how: "44,573.44" in most of the world,
|
|
65
|
+
* "44.573,44" across much of Europe, and "1,22,464.06" under the Indian lakh convention. Matching only
|
|
66
|
+
* the ungrouped form finds nothing on any app that formats its money, which is all of them — measured
|
|
67
|
+
* on a logistics console whose cells read "₹44,573.44" while the check searched for "44573.44".
|
|
68
|
+
*/
|
|
69
|
+
function majorForms(minor, currency) {
|
|
70
|
+
if (ZERO_DECIMAL.has(currency))
|
|
71
|
+
return [String(minor), group(String(minor)), indian(String(minor))];
|
|
72
|
+
const plain = (minor / 100).toFixed(2);
|
|
73
|
+
const [whole = '', fraction = ''] = plain.split('.');
|
|
74
|
+
return [
|
|
75
|
+
plain,
|
|
76
|
+
`${group(whole)}.${fraction}`,
|
|
77
|
+
`${indian(whole)}.${fraction}`,
|
|
78
|
+
// European: dots group, comma is the decimal separator.
|
|
79
|
+
`${group(whole).replace(/,/g, '.')},${fraction}`,
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
/** Western grouping: every three digits from the right. */
|
|
83
|
+
function group(whole) {
|
|
84
|
+
return whole.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
85
|
+
}
|
|
86
|
+
/** Indian grouping: the last three digits, then pairs — 1,22,464. */
|
|
87
|
+
function indian(whole) {
|
|
88
|
+
if (whole.length <= 3)
|
|
89
|
+
return whole;
|
|
90
|
+
const last = whole.slice(-3);
|
|
91
|
+
const rest = whole.slice(0, -3);
|
|
92
|
+
return `${rest.replace(/\B(?=(\d{2})+(?!\d))/g, ',')},${last}`;
|
|
93
|
+
}
|
|
94
|
+
/** Does the text show this amount marked as some OTHER currency? Returns that marker. */
|
|
95
|
+
function wrongCurrencyMark(text, amounts, expected) {
|
|
96
|
+
for (const amount of amounts) {
|
|
97
|
+
const mark = markBefore(text, amount, expected);
|
|
98
|
+
if (mark !== undefined)
|
|
99
|
+
return mark;
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
function markBefore(text, amount, expected) {
|
|
104
|
+
const at = text.indexOf(amount);
|
|
105
|
+
if (at === -1)
|
|
106
|
+
return undefined; // this rendering is not on screen — try the next
|
|
107
|
+
// Look just before the number, where a symbol or code sits. A window, not the whole page: the
|
|
108
|
+
// point is what is attached to THIS amount.
|
|
109
|
+
const before = text.slice(Math.max(0, at - 12), at);
|
|
110
|
+
const expectedMarks = CURRENCY_MARKS[expected] ?? [expected];
|
|
111
|
+
if (expectedMarks.some((mark) => before.includes(mark)))
|
|
112
|
+
return undefined;
|
|
113
|
+
for (const [code, marks] of Object.entries(CURRENCY_MARKS)) {
|
|
114
|
+
if (code === expected)
|
|
115
|
+
continue;
|
|
116
|
+
for (const mark of marks) {
|
|
117
|
+
if (before.includes(mark))
|
|
118
|
+
return mark;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
/** Every form a state value might legitimately be rendered as: `on_hold`, `on hold`, `On Hold`. */
|
|
124
|
+
function renderedForms(value) {
|
|
125
|
+
const spaced = value.replace(/[_-]+/g, ' ');
|
|
126
|
+
return [value, spaced, spaced.replace(/\s+/g, '')];
|
|
127
|
+
}
|
|
128
|
+
function shows(text, value) {
|
|
129
|
+
const haystack = text.toLowerCase();
|
|
130
|
+
return renderedForms(value).some((form) => haystack.includes(form.toLowerCase()));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Compare entities against the page's rendered text.
|
|
134
|
+
*
|
|
135
|
+
* `pageText` is the visible text of the page — the snapshot's own tree is exactly this, so no new
|
|
136
|
+
* capability is needed to obtain it.
|
|
137
|
+
*/
|
|
138
|
+
export function reconcile(bodies, pageText) {
|
|
139
|
+
const entities = bodies.flatMap((body) => entitiesIn(body));
|
|
140
|
+
const found = [];
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
// Every value each state field took across the response set — used to tell "this value is simply
|
|
143
|
+
// not rendered anywhere" (uninteresting) from "a DIFFERENT value of this same field is rendered"
|
|
144
|
+
// (a mapping bug). Without that distinction the check would flag any field a UI chooses not to show.
|
|
145
|
+
const vocabulary = new Map();
|
|
146
|
+
for (const entity of entities) {
|
|
147
|
+
for (const [field, value] of Object.entries(entity)) {
|
|
148
|
+
if (!STATE_FIELDS.has(field) || typeof value !== 'string')
|
|
149
|
+
continue;
|
|
150
|
+
const values = vocabulary.get(field) ?? new Set();
|
|
151
|
+
values.add(value);
|
|
152
|
+
vocabulary.set(field, values);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for (const entity of entities) {
|
|
156
|
+
// An id is a string or a number in every API worth reconciling; anything else is not an identity
|
|
157
|
+
// this can report against, so the record is skipped rather than stringified into nonsense.
|
|
158
|
+
const raw = entity['id'];
|
|
159
|
+
if (typeof raw !== 'string' && typeof raw !== 'number')
|
|
160
|
+
continue;
|
|
161
|
+
const id = String(raw);
|
|
162
|
+
// Any numeric field on an entity that declares a currency is money in THAT currency. Checking a
|
|
163
|
+
// fixed field name instead would only ever work on APIs that happen to call it `amount` — the
|
|
164
|
+
// three real ones measured here use `amount`, `declaredValueMinor` and `total`.
|
|
165
|
+
//
|
|
166
|
+
// The check is still tight, because a value only counts when the page renders it AS money: the
|
|
167
|
+
// number must appear with a currency marker attached. A non-money number that happens to share a
|
|
168
|
+
// decimal form (a weight, a count) is rendered bare and never reaches the comparison.
|
|
169
|
+
const currency = entity['currency'];
|
|
170
|
+
if (typeof currency === 'string') {
|
|
171
|
+
for (const [field, value] of Object.entries(entity)) {
|
|
172
|
+
if (typeof value !== 'number' || field === 'currency')
|
|
173
|
+
continue;
|
|
174
|
+
const printed = majorForms(value, currency);
|
|
175
|
+
const wrong = wrongCurrencyMark(pageText, printed, currency);
|
|
176
|
+
const key = `${id}:currency`;
|
|
177
|
+
if (wrong === undefined || seen.has(key))
|
|
178
|
+
continue;
|
|
179
|
+
seen.add(key);
|
|
180
|
+
found.push({
|
|
181
|
+
entity: id,
|
|
182
|
+
field: 'currency',
|
|
183
|
+
api: currency,
|
|
184
|
+
rendered: wrong,
|
|
185
|
+
why: `the API reports ${currency} ${String(value)} (minor units, field "${field}") and the page shows it marked "${wrong}" — the number is right and the currency is not, so the amount reads as a different one entirely`,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
for (const [field, value] of Object.entries(entity)) {
|
|
190
|
+
if (!STATE_FIELDS.has(field) || typeof value !== 'string')
|
|
191
|
+
continue;
|
|
192
|
+
if (shows(pageText, value))
|
|
193
|
+
continue;
|
|
194
|
+
// Only interesting if the page renders SOME value of this field — otherwise the UI just does
|
|
195
|
+
// not display it, which is a design choice and not a defect.
|
|
196
|
+
const others = [...(vocabulary.get(field) ?? [])].filter((v) => v !== value);
|
|
197
|
+
const shown = others.filter((v) => shows(pageText, v));
|
|
198
|
+
const key = `${id}:${field}`;
|
|
199
|
+
if (shown.length === 0 || seen.has(key))
|
|
200
|
+
continue;
|
|
201
|
+
seen.add(key);
|
|
202
|
+
found.push({
|
|
203
|
+
entity: id,
|
|
204
|
+
field,
|
|
205
|
+
api: value,
|
|
206
|
+
why: `the API reports ${field}="${value}" for ${id}, and no rendering of that value appears on the page — while other values of the same field are shown (${shown.join(', ')}), so this record is being displayed as something it is not`,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return found;
|
|
211
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ReticleEvent } from '@reticlehq/core';
|
|
2
|
+
import type { Contradiction } from './contradictions.js';
|
|
3
|
+
/** The one contradiction, or none. Reported once per window — a race is a fact about the window. */
|
|
4
|
+
export declare function findStaleResponses(events: readonly ReticleEvent[]): Contradiction[];
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { ContradictionKind, EventType } from '@reticlehq/core';
|
|
2
|
+
function pathOf(url) {
|
|
3
|
+
const q = url.indexOf('?');
|
|
4
|
+
return q === -1 ? url : url.slice(0, q);
|
|
5
|
+
}
|
|
6
|
+
function queryOf(url) {
|
|
7
|
+
const q = url.indexOf('?');
|
|
8
|
+
return q === -1 ? '' : url.slice(q + 1);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parameters that ENUMERATE a collection rather than select within it.
|
|
12
|
+
*
|
|
13
|
+
* Two overlapping reads that differ only in these are a fan-out, not a race: an app fetching page 0
|
|
14
|
+
* and page 5 in parallel wants both, and whichever lands last is not overwriting the other. Measured
|
|
15
|
+
* against a synthetic dashboard doing parallel pagination — every run reported a race, because the
|
|
16
|
+
* requests do overlap and do settle out of order, and the original rule looked no further than that.
|
|
17
|
+
*
|
|
18
|
+
* A filter race differs in a SELECTING parameter (`status`, `q`, a date range): the second request
|
|
19
|
+
* makes the first obsolete, both answer into the same view, and the slower one wins.
|
|
20
|
+
*/
|
|
21
|
+
const ENUMERATING_PARAMS = new Set([
|
|
22
|
+
'page',
|
|
23
|
+
'offset',
|
|
24
|
+
'cursor',
|
|
25
|
+
'limit',
|
|
26
|
+
'size',
|
|
27
|
+
'per_page',
|
|
28
|
+
'perPage',
|
|
29
|
+
'pageSize',
|
|
30
|
+
'after',
|
|
31
|
+
'before',
|
|
32
|
+
'start',
|
|
33
|
+
'end',
|
|
34
|
+
]);
|
|
35
|
+
/** Whether the two queries differ ONLY in how much of the same collection they ask for. */
|
|
36
|
+
function differsOnlyByEnumeration(first, second) {
|
|
37
|
+
const a = new URLSearchParams(first);
|
|
38
|
+
const b = new URLSearchParams(second);
|
|
39
|
+
const keys = new Set([...a.keys(), ...b.keys()]);
|
|
40
|
+
let differed = false;
|
|
41
|
+
for (const key of keys) {
|
|
42
|
+
if (a.get(key) === b.get(key))
|
|
43
|
+
continue;
|
|
44
|
+
if (!ENUMERATING_PARAMS.has(key))
|
|
45
|
+
return false;
|
|
46
|
+
differed = true;
|
|
47
|
+
}
|
|
48
|
+
return differed;
|
|
49
|
+
}
|
|
50
|
+
/** Pair NET_PENDING with its NET_REQUEST by the id both carry. Unpaired events are still in flight. */
|
|
51
|
+
function flights(events) {
|
|
52
|
+
const issued = new Map();
|
|
53
|
+
const out = [];
|
|
54
|
+
for (const event of events) {
|
|
55
|
+
const id = typeof event.data['id'] === 'string' ? event.data['id'] : undefined;
|
|
56
|
+
if (id === undefined)
|
|
57
|
+
continue;
|
|
58
|
+
const at = typeof event.t === 'number' ? event.t : 0;
|
|
59
|
+
if (event.type === EventType.NET_PENDING) {
|
|
60
|
+
const url = typeof event.data['url'] === 'string' ? event.data['url'] : '';
|
|
61
|
+
const method = typeof event.data['method'] === 'string' ? event.data['method'] : 'GET';
|
|
62
|
+
issued.set(id, { url, method, at });
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (event.type !== EventType.NET_REQUEST)
|
|
66
|
+
continue;
|
|
67
|
+
const start = issued.get(id);
|
|
68
|
+
if (start === undefined)
|
|
69
|
+
continue; // started before this window — its ordering is unknowable
|
|
70
|
+
out.push({
|
|
71
|
+
id,
|
|
72
|
+
url: start.url,
|
|
73
|
+
method: start.method.toUpperCase(),
|
|
74
|
+
issuedAt: start.at,
|
|
75
|
+
settledAt: at,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Most recent flights compared per endpoint.
|
|
82
|
+
*
|
|
83
|
+
* The comparison is pairwise, so an unbounded window is quadratic: measured at 548ms for 2000
|
|
84
|
+
* concurrent reads of one endpoint, paid on EVERY verdict. A verdict that costs half a second is a
|
|
85
|
+
* verdict agents stop asking for. Racing requests are adjacent in time by definition, so the most
|
|
86
|
+
* recent flights are where a race can be; older ones cannot overlap them anyway.
|
|
87
|
+
*
|
|
88
|
+
* Bounded rather than silent: this is a detection ceiling, not a truncation of reported data — a race
|
|
89
|
+
* among more than this many simultaneous reads of ONE endpoint goes unreported, and that is a
|
|
90
|
+
* deliberate trade for a hot path that runs on every assert.
|
|
91
|
+
*/
|
|
92
|
+
const MAX_FLIGHTS_PER_PATH = 60;
|
|
93
|
+
/** The one contradiction, or none. Reported once per window — a race is a fact about the window. */
|
|
94
|
+
export function findStaleResponses(events) {
|
|
95
|
+
// Grouped by path first: two reads of DIFFERENT endpoints can never race, and comparing them was
|
|
96
|
+
// the bulk of the work on any real page, which talks to many endpoints at once.
|
|
97
|
+
const byPath = new Map();
|
|
98
|
+
for (const flight of flights(events)) {
|
|
99
|
+
if (flight.method !== 'GET')
|
|
100
|
+
continue;
|
|
101
|
+
const path = pathOf(flight.url);
|
|
102
|
+
const group = byPath.get(path) ?? [];
|
|
103
|
+
group.push(flight);
|
|
104
|
+
byPath.set(path, group);
|
|
105
|
+
}
|
|
106
|
+
for (const group of byPath.values()) {
|
|
107
|
+
const reads = group.slice(-MAX_FLIGHTS_PER_PATH);
|
|
108
|
+
const found = raceIn(reads);
|
|
109
|
+
if (found !== undefined)
|
|
110
|
+
return [found];
|
|
111
|
+
}
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
function raceIn(reads) {
|
|
115
|
+
for (const first of reads) {
|
|
116
|
+
for (const second of reads) {
|
|
117
|
+
if (first.id === second.id)
|
|
118
|
+
continue;
|
|
119
|
+
if (second.issuedAt <= first.issuedAt)
|
|
120
|
+
continue; // `first` must be the earlier request
|
|
121
|
+
if (second.issuedAt >= first.settledAt)
|
|
122
|
+
continue; // sequential, not overlapping
|
|
123
|
+
if (second.settledAt >= first.settledAt)
|
|
124
|
+
continue; // settled in order — no race
|
|
125
|
+
if (queryOf(first.url) === queryOf(second.url))
|
|
126
|
+
continue; // a retry, not a superseding query
|
|
127
|
+
// Parallel pagination is not a race — see differsOnlyByEnumeration.
|
|
128
|
+
if (differsOnlyByEnumeration(queryOf(first.url), queryOf(second.url)))
|
|
129
|
+
continue;
|
|
130
|
+
return {
|
|
131
|
+
kind: ContradictionKind.STALE_RESPONSE_APPLIED,
|
|
132
|
+
claim: `both reads of ${pathOf(first.url)} returned successfully and the page settled`,
|
|
133
|
+
counter: `the SUPERSEDED request (${queryOf(first.url)}) settled ${String(Math.max(0, first.settledAt - second.settledAt))}ms AFTER the one that replaced it (${queryOf(second.url)}), so the screen is showing the older query's data`,
|
|
134
|
+
detail: 'a filter/search race: the newer request was issued while the older was still in flight, and the older answered last, so whichever query the server happens to be slower at wins — unless the app cancels superseded requests or drops responses that no longer match current state',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ReticleEvent } from '@reticlehq/core';
|
|
2
|
+
import type { Contradiction } from './contradictions.js';
|
|
3
|
+
/**
|
|
4
|
+
* @param prior events from BEFORE the window, used only to learn what the API has stated.
|
|
5
|
+
*
|
|
6
|
+
* The scale error is a disagreement with a value the API gave EARLIER — by definition before the
|
|
7
|
+
* action that sends it back. `reticle_assert` scopes its window to the last act's cursor, so the
|
|
8
|
+
* stating response falls outside it and `known` is empty exactly when it matters. Measured live on
|
|
9
|
+
* the payments panel: `reticle_observe` over a wide window reported the mismatch while `assert` on
|
|
10
|
+
* the same session reported none — the verdict tool, the one an agent gates on, was the blind one.
|
|
11
|
+
*
|
|
12
|
+
* Prior events TEACH, they are never themselves findings: widening the window instead would make
|
|
13
|
+
* every other rule blame this action for events it did not cause, and would re-report every earlier
|
|
14
|
+
* mistake on every assert.
|
|
15
|
+
*/
|
|
16
|
+
export declare function findUnitMismatches(events: readonly ReticleEvent[], prior?: readonly ReticleEvent[]): Contradiction[];
|