Two corrections to the first pass, both of which would have produced a
false positive.
1) A non-empty body is NOT proof of access. PropLine's free tier returns
the full STRUCTURE of tier-gated endpoints with values stripped plus an
upgrade_url -- and the first pass classified /odds/closing and /movement
as "works" on structure alone. detectRedaction() now counts actual
prices and downgrades works -> partial when a body advertises an upgrade
or carries outcomes with zero prices. Same class as the harness that
returned a silent false, inverted.
2) One hard-coded reference set forces a yes/no on a question that is
really a curve. reference_policy_curve reports strict eligibility
(>=2 books, both sides, same line) under exchange_only /
exchange_plus_sharp / exchange_plus_us / takeable_only, so the ruler
decision is made on coverage-vs-quality rather than on a guess. DFS is
absent from every policy by construction and a test asserts it.
Also probes /markets/resolution-summary: /exports/resolved-props being 403
tells us we cannot PULL settlements; resolution-summary tells us whether
they EXIST to be bought. Different questions.
19 unit tests, still hermetic.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
Adds GET /api/internal/propline-verify (internal-key gated, read-only) so
Phase 1 can run WHERE THE KEY LIVES. Touches no cache, no ledger, no
grade; the live adapter and the live ruler are untouched. Breadth reuses
proplineAdapter.fetchRaw -- the exact live request -- so what it measures
is what the pipeline actually receives.
Reports per sport (never pooled): books/prop from the feed vs after our
own ALLOWED_BOOKS, props made INVISIBLE by that filter, reference-book
presence, DFS presence reported separately, and consensus eligibility.
Consensus eligibility is deliberately strict: >=2 REFERENCE books posting
BOTH sides at the SAME line. A one-sided quote cannot be de-vigged, and
two books at different lines are not the same market -- counting either
would overstate how much of the slate can carry a real ruler.
Probes the documented-but-unverified endpoints (/sports, /context,
/odds/closing, /movement, /results, /exports/resolved-props for four sport
keys) and classifies works/partial/no, with 403 = tier-gated and 200-but-
empty = partial rather than works.
Key safety is the other locked property: the key goes via axios params,
never string-interpolated, and every emitted string passes scrubKeys()
which removes the literal key AND any surviving apiKey= query value. A
test asserts a thrown transport error carrying the key cannot escape.
13 unit tests, hermetic (no network, no key).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc