@reticlehq/server 2.2.1 → 2.4.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 +102 -12
- package/dist/bridge.test-harness.d.ts +5 -1
- package/dist/bridge.test-harness.js +12 -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 +47 -3
- package/dist/cli-parse.js +147 -13
- package/dist/cli-port.d.ts +3 -1
- package/dist/cli-port.js +21 -1
- package/dist/cli-verify.d.ts +0 -1
- package/dist/cli-verify.js +1 -2
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +88 -69
- package/dist/cloud/cloud-config.d.ts +0 -1
- package/dist/cloud/cloud-config.js +0 -1
- package/dist/cloud/cloud-sync.d.ts +4 -5
- package/dist/cloud/cloud-sync.js +0 -1
- package/dist/cloud-cli.d.ts +0 -1
- package/dist/cloud-cli.js +9 -2
- 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 +36 -24
- 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 -7
- package/dist/flows/decision.js +47 -7
- 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 +2 -3
- 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 +44 -14
- 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 +0 -1
- package/dist/flows/server-verify.js +0 -1
- 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/capabilities.d.ts +17 -0
- package/dist/init/capabilities.js +51 -0
- 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 +25 -1
- package/dist/init/detect.js +65 -4
- package/dist/init/mcp.d.ts +0 -1
- package/dist/init/mcp.js +0 -1
- package/dist/init/next-patch.d.ts +41 -0
- package/dist/init/next-patch.js +106 -0
- package/dist/init/node-io.d.ts +0 -1
- package/dist/init/node-io.js +25 -1
- package/dist/init/patch-kind.d.ts +20 -0
- package/dist/init/patch-kind.js +10 -0
- package/dist/init/plan-framework.d.ts +35 -0
- package/dist/init/plan-framework.js +217 -0
- package/dist/init/plan.d.ts +55 -4
- package/dist/init/plan.js +130 -112
- package/dist/init/project-id.d.ts +0 -1
- package/dist/init/project-id.js +0 -1
- package/dist/init/run.d.ts +17 -1
- package/dist/init/run.js +237 -18
- package/dist/init/slash-command.d.ts +18 -0
- package/dist/init/slash-command.js +52 -0
- package/dist/init/snippets.d.ts +48 -3
- package/dist/init/snippets.js +156 -6
- package/dist/init/vite-config.d.ts +4 -11
- package/dist/init/vite-config.js +15 -8
- package/dist/input/driven-redaction.d.ts +15 -0
- package/dist/input/driven-redaction.js +76 -0
- package/dist/input/network-detail.d.ts +9 -4
- package/dist/input/network-detail.js +35 -16
- 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 +35 -4
- package/dist/mcp-proxy.js +174 -28
- 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 +0 -1
- package/dist/pool/playwright-launcher.js +10 -2
- package/dist/project/baselines.d.ts +0 -1
- package/dist/project/baselines.js +0 -1
- 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 +0 -1
- package/dist/project/project-tools.d.ts +0 -1
- package/dist/project/project-tools.js +29 -4
- 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 +0 -1
- package/dist/runs/verification-sync.js +0 -1
- 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 +35 -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 +33 -53
- package/dist/session/session.js +102 -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 +35 -8
- package/dist/telemetry/telemetry.js +105 -12
- 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 +115 -5
- 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 +14 -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 +0 -1
- 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-config.d.ts.map +0 -1
- package/dist/cloud/cloud-config.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/cloud-cli.d.ts.map +0 -1
- package/dist/cloud-cli.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/server-verify.d.ts.map +0 -1
- package/dist/flows/server-verify.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/verification-sync.d.ts.map +0 -1
- package/dist/runs/verification-sync.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/telemetry/telemetry.d.ts.map +0 -1
- package/dist/telemetry/telemetry.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
|
@@ -42,8 +42,15 @@ export function eventMatchesFilters(e, filters) {
|
|
|
42
42
|
}
|
|
43
43
|
return false;
|
|
44
44
|
}
|
|
45
|
-
/**
|
|
46
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Match a net.request event against optional method/url/status/ok filters (reticle_network).
|
|
47
|
+
*
|
|
48
|
+
* `ok` is the filter the tool's own description points at for desktop IPC, whose 200/500 is derived
|
|
49
|
+
* rather than sent by any server. A still-PENDING call never matches an `ok` filter in either
|
|
50
|
+
* direction: unresolved is not the same as failed, and answering "yes, it failed" about a request
|
|
51
|
+
* still in flight is the false green this whole surface exists to prevent.
|
|
52
|
+
*/
|
|
53
|
+
export function matchNet(e, method, urlContains, status, ok) {
|
|
47
54
|
const d = e.data;
|
|
48
55
|
if (method !== undefined && asString(d['method'])?.toUpperCase() !== method.toUpperCase()) {
|
|
49
56
|
return false;
|
|
@@ -53,6 +60,8 @@ export function matchNet(e, method, urlContains, status) {
|
|
|
53
60
|
}
|
|
54
61
|
if (status !== undefined && asNumber(d['status']) !== status)
|
|
55
62
|
return false;
|
|
63
|
+
if (ok !== undefined && d['ok'] !== ok)
|
|
64
|
+
return false;
|
|
56
65
|
return true;
|
|
57
66
|
}
|
|
58
67
|
/**
|
|
@@ -106,6 +115,8 @@ export function projectNetCall(e) {
|
|
|
106
115
|
}
|
|
107
116
|
if (ms !== undefined)
|
|
108
117
|
view.ms = ms;
|
|
118
|
+
if (e.data['oneWay'] === true)
|
|
119
|
+
view.oneWay = true;
|
|
109
120
|
const headers = e.data['headers'];
|
|
110
121
|
if (headers !== null && typeof headers === 'object') {
|
|
111
122
|
view.headers = headers;
|
|
@@ -182,4 +193,3 @@ export function consoleEmptyHint(allConsole) {
|
|
|
182
193
|
}
|
|
183
194
|
return { totalInWindow: allConsole.length, byLevel };
|
|
184
195
|
}
|
|
185
|
-
//# sourceMappingURL=event-filters.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read what CAN be read out of a truncated JSON payload.
|
|
3
|
+
*
|
|
4
|
+
* Response bodies are capped (8192 bytes) so one enormous payload cannot blow the transport budget.
|
|
5
|
+
* The cap is right; treating what it produces as "no body" is not. Measured on a 10,000-row console:
|
|
6
|
+
* the list response is 19,932 bytes, arrives truncated mid-object, `JSON.parse` throws, and every
|
|
7
|
+
* body-based check — currency reconciliation, per-item failures, unit mismatches — silently reported
|
|
8
|
+
* nothing on the one page with the most data to be wrong about.
|
|
9
|
+
*
|
|
10
|
+
* Worse, the tool then advised turning on body capture, which was already on. Advice that cannot
|
|
11
|
+
* work is more expensive than silence: it sends the reader to change a setting that is already
|
|
12
|
+
* correct, and teaches them the tool does not know what it is looking at.
|
|
13
|
+
*
|
|
14
|
+
* A truncated array of records is still mostly complete records. This recovers the ones that closed
|
|
15
|
+
* before the cut and reports how many it had to drop, so a partial answer is delivered AS partial
|
|
16
|
+
* rather than as a clean one.
|
|
17
|
+
*/
|
|
18
|
+
export interface Salvaged {
|
|
19
|
+
/** Parsed values — one whole body, or the records recovered from a truncated one. */
|
|
20
|
+
values: unknown[];
|
|
21
|
+
/** True when the payload was incomplete and only part of it could be read. */
|
|
22
|
+
partial: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parse a body, falling back to whole records when it was cut short.
|
|
26
|
+
*
|
|
27
|
+
* The nesting caveat is real and stated rather than hidden: an object cut mid-nesting contributes its
|
|
28
|
+
* completed CHILDREN instead of itself, so a salvaged set can hold records the envelope would have
|
|
29
|
+
* grouped differently. For field-level comparison — is this id's currency rendered correctly — that
|
|
30
|
+
* costs nothing, because each record still carries its own id and its own fields.
|
|
31
|
+
*/
|
|
32
|
+
export declare function salvageJson(raw: string): Salvaged;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read what CAN be read out of a truncated JSON payload.
|
|
3
|
+
*
|
|
4
|
+
* Response bodies are capped (8192 bytes) so one enormous payload cannot blow the transport budget.
|
|
5
|
+
* The cap is right; treating what it produces as "no body" is not. Measured on a 10,000-row console:
|
|
6
|
+
* the list response is 19,932 bytes, arrives truncated mid-object, `JSON.parse` throws, and every
|
|
7
|
+
* body-based check — currency reconciliation, per-item failures, unit mismatches — silently reported
|
|
8
|
+
* nothing on the one page with the most data to be wrong about.
|
|
9
|
+
*
|
|
10
|
+
* Worse, the tool then advised turning on body capture, which was already on. Advice that cannot
|
|
11
|
+
* work is more expensive than silence: it sends the reader to change a setting that is already
|
|
12
|
+
* correct, and teaches them the tool does not know what it is looking at.
|
|
13
|
+
*
|
|
14
|
+
* A truncated array of records is still mostly complete records. This recovers the ones that closed
|
|
15
|
+
* before the cut and reports how many it had to drop, so a partial answer is delivered AS partial
|
|
16
|
+
* rather than as a clean one.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Complete `{...}` objects inside a truncated payload, in order.
|
|
20
|
+
*
|
|
21
|
+
* NOT top-level ones: a cut body is typically `{"rows":[{…},{…},{…` where the OUTER brace never
|
|
22
|
+
* closes, so nothing at depth 0 completes and a depth-0 scan recovers exactly nothing. The records
|
|
23
|
+
* are one level in.
|
|
24
|
+
*
|
|
25
|
+
* Objects are kept only at the SHALLOWEST depth at which anything closes — that is the record level.
|
|
26
|
+
* Keeping every depth would also return each record's nested children (a shipment's legs, an order's
|
|
27
|
+
* line items), and those carry their own `id` and `status`, which would pollute the comparison with
|
|
28
|
+
* values that were never meant to be rendered as rows.
|
|
29
|
+
*/
|
|
30
|
+
function completeObjects(text) {
|
|
31
|
+
const found = [];
|
|
32
|
+
let depth = 0;
|
|
33
|
+
const starts = [];
|
|
34
|
+
let inString = false;
|
|
35
|
+
let escaped = false;
|
|
36
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
37
|
+
const ch = text[i];
|
|
38
|
+
if (inString) {
|
|
39
|
+
if (escaped)
|
|
40
|
+
escaped = false;
|
|
41
|
+
else if (ch === '\\')
|
|
42
|
+
escaped = true;
|
|
43
|
+
else if (ch === '"')
|
|
44
|
+
inString = false;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (ch === '"') {
|
|
48
|
+
inString = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (ch === '{') {
|
|
52
|
+
starts.push(i);
|
|
53
|
+
depth += 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (ch === '}') {
|
|
57
|
+
depth -= 1;
|
|
58
|
+
// A negative depth means the cut removed an opening brace; nothing further is trustworthy.
|
|
59
|
+
if (depth < 0)
|
|
60
|
+
break;
|
|
61
|
+
const start = starts.pop();
|
|
62
|
+
if (start !== undefined)
|
|
63
|
+
found.push({ depth: depth + 1, text: text.slice(start, i + 1) });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (found.length === 0)
|
|
67
|
+
return [];
|
|
68
|
+
const shallowest = Math.min(...found.map((f) => f.depth));
|
|
69
|
+
return found.filter((f) => f.depth === shallowest).map((f) => f.text);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Parse a body, falling back to whole records when it was cut short.
|
|
73
|
+
*
|
|
74
|
+
* The nesting caveat is real and stated rather than hidden: an object cut mid-nesting contributes its
|
|
75
|
+
* completed CHILDREN instead of itself, so a salvaged set can hold records the envelope would have
|
|
76
|
+
* grouped differently. For field-level comparison — is this id's currency rendered correctly — that
|
|
77
|
+
* costs nothing, because each record still carries its own id and its own fields.
|
|
78
|
+
*/
|
|
79
|
+
export function salvageJson(raw) {
|
|
80
|
+
try {
|
|
81
|
+
return { values: [JSON.parse(raw)], partial: false };
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// fall through to record-level recovery
|
|
85
|
+
}
|
|
86
|
+
const values = [];
|
|
87
|
+
for (const chunk of completeObjects(raw)) {
|
|
88
|
+
try {
|
|
89
|
+
values.push(JSON.parse(chunk));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// a chunk that still will not parse is simply not recovered
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { values, partial: true };
|
|
96
|
+
}
|
|
@@ -15,6 +15,8 @@ export type Predicate = {
|
|
|
15
15
|
method?: string;
|
|
16
16
|
urlContains?: string;
|
|
17
17
|
status?: number;
|
|
18
|
+
/** Did the call succeed? The honest field for IPC, which has no status code. */
|
|
19
|
+
ok?: boolean;
|
|
18
20
|
since?: number;
|
|
19
21
|
count?: number;
|
|
20
22
|
} | {
|
|
@@ -103,4 +105,3 @@ export declare function evalSignal(events: ReticleEvent[], p: Extract<Predicate,
|
|
|
103
105
|
export declare function evalSettled(events: ReticleEvent[], p: Extract<Predicate, {
|
|
104
106
|
kind: 'settled';
|
|
105
107
|
}>, now: number): EvalResult;
|
|
106
|
-
//# sourceMappingURL=predicate-eval.d.ts.map
|
|
@@ -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
|
+
}
|