feat(courtlistener): add /api/proxy/courtlistener proxy for ChittyCounsel#234
feat(courtlistener): add /api/proxy/courtlistener proxy for ChittyCounsel#234chitcommit wants to merge 2 commits into
Conversation
…nsel Adds a CourtListener REST v4 proxy mirroring the established google.js route pattern, with CourtListener-specific behaviors per spec: - Token auth uses `Authorization: Token <key>` (CL convention, not Bearer) - Token resolved from `env.COURTLISTENER_API_TOKEN` (Cloudflare Secrets Store binding declared in wrangler.jsonc for top-level + staging + production envs against store e914522471964c3c8cf1e601770edcc3), falling back to the credential broker - Fail-closed 503 `POLICY_BLOCKED_CHITTYCONNECT_UNAVAILABLE` when token is not provisioned (no operator credential paste path) - Resource allowlist enforced before any upstream call: search, opinions, clusters, dockets, docket-entries, recap-documents, audio, citation-lookup, people, courts - Routes: GET /search, GET /:resource[/:id], POST /citation-lookup - 15-min Cache API wrap on GETs; bypass via `X-Bypass-Cache: 1` - Upstream error bodies truncated to 200 chars; token never echoed Consumer: ChittyCounsel (chittycorp/CHITTYCOUNSEL PR #4). Synthetic env credential plane status: binding declared in all three env blocks (top-level + staging + production). The secret VALUE `COURTLISTENER_API_TOKEN` is NOT yet present in Cloudflare Secrets Store e914522471964c3c8cf1e601770edcc3 — verified via `wrangler secrets-store secret list`. Until chico/credential provisioner writes the value, the 503 fail-closed path is exercised. Deploy is safe and non-disruptive (no behavior change to existing routes); operator approval still required before `npm run deploy:production`. Test coverage: 21 unit tests covering token resolution (3 sources + fail-closed), search, allowlist enforcement (incl. 404 on unknown resource without upstream call), citation-lookup POST forwarding, Cache API HIT/MISS + bypass, upstream error truncation, and Token (not Bearer) auth scheme. Mirrors established google-routes.test.js pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
chittyconnect | 812b076 | Jun 04 2026, 10:15 AM |
|
Warning Review limit reached
More reviews will be available in 44 minutes and 49 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds a CourtListener REST API proxy with token-based authentication, resource allowlisting, and optional response caching. Includes router integration, configuration management, OpenAPI documentation, and comprehensive test coverage for token resolution, endpoint forwarding, cache behavior, and security controls. ChangesCourtListener Proxy Implementation
Sequence DiagramsequenceDiagram
participant Client
participant ProxyRouter
participant TokenResolver
participant UpstreamAPI
participant Cache as Cache API
Client->>ProxyRouter: GET /search?q=term
ProxyRouter->>TokenResolver: getCourtListenerToken()
TokenResolver-->>ProxyRouter: token
ProxyRouter->>Cache: Check cache
Cache-->>ProxyRouter: miss
ProxyRouter->>UpstreamAPI: GET /search/?q=term + Authorization: Token
UpstreamAPI-->>ProxyRouter: 200 JSON
ProxyRouter->>Cache: Store with TTL
ProxyRouter-->>Client: 200 JSON + X-Cache: MISS
Client->>ProxyRouter: POST /citation-lookup
ProxyRouter->>TokenResolver: getCourtListenerToken()
TokenResolver-->>ProxyRouter: token
ProxyRouter->>UpstreamAPI: POST /citation-lookup/ + body + Authorization: Token
UpstreamAPI-->>ProxyRouter: 200 citations
ProxyRouter-->>Client: 200 citations
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Status — credential lane wired, awaiting non-paste secret populationChittyConnect concierge re-dispatch checkpoint: Exhaustive credential search results (no CourtListener token exists on this infrastructure):
What's ready to merge (this PR): route + tests + wrangler binding all green on Test/Lint/CodeQL/Security/compliance. Code is fail-closed — when Remaining blockers (both require human signoff, neither requires a chat paste):
Pairs with: chittycorp/CHITTYCOUNSEL PR #4 (counsel-side Reference: |
There was a problem hiding this comment.
Pull request overview
Adds a new CourtListener REST v4 proxy under /api/proxy/courtlistener/* to support ChittyCounsel, following the established proxy patterns (token injection, allowlisting, cache wrapping, OpenAPI docs, and unit tests).
Changes:
- Adds
src/api/routes/courtlistener.jsimplementing GET/search, GET/:resource[/:id](allowlisted), and POST/citation-lookupwith token resolution and Cache API wrapping for GETs. - Mounts the new proxy router at
/api/proxy/courtlistenerand documents the endpoints inpublic/openapi.json. - Adds CourtListener secrets-store bindings to
wrangler.jsoncand introduces a new unit test suite for the route.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
src/api/routes/courtlistener.js |
New CourtListener proxy implementation (token resolution, allowlist, cache wrap, error handling). |
src/api/router.js |
Mounts the CourtListener proxy router under /api/proxy/courtlistener. |
wrangler.jsonc |
Adds COURTLISTENER_API_TOKEN secrets-store binding at top-level + staging + production. |
public/openapi.json |
Documents CourtListener proxy endpoints and parameters. |
tests/api/courtlistener-routes.test.js |
Adds route-level unit tests mirroring the existing proxy testing approach. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function buildUpstream(resource, id, search) { | ||
| const path = id ? `${resource}/${encodeURIComponent(id)}` : `${resource}`; | ||
| const qs = search ? (search.startsWith("?") ? search : `?${search}`) : ""; | ||
| return `${CL_API}/${path}/${qs}`; | ||
| } |
| if (hit) { | ||
| // Clone — Cache API responses are immutable | ||
| const cloned = new Response(hit.body, hit); | ||
| cloned.headers.set("X-Cache", "HIT"); | ||
| return cloned; |
| const body = await upstream.text().catch(() => ""); | ||
| return c.json( | ||
| { error: `CourtListener API ${upstream.status}: ${body.slice(0, 200)}` }, | ||
| upstream.status, | ||
| ); |
| const text = await upstream.text().catch(() => ""); | ||
| return c.json( | ||
| { error: `CourtListener API ${upstream.status}: ${text.slice(0, 200)}` }, | ||
| upstream.status, | ||
| ); |
| const body = await res.json(); | ||
| // Truncation may still surface the token from upstream — but the proxy itself | ||
| // does not add it. Assert no extra header echo and the token isn't in error key path. | ||
| expect(body.error).not.toContain("Authorization"); |
| { "binding": "CHITTYAUTH_ISSUED_MINT_TOKEN", "store_id": "e914522471964c3c8cf1e601770edcc3", "secret_name": "chittyauth_issued_mint_api_key" }, | ||
| { "binding": "MINT_API_KEY", "store_id": "e914522471964c3c8cf1e601770edcc3", "secret_name": "chittyauth_issued_mint_api_key" } | ||
| { "binding": "MINT_API_KEY", "store_id": "e914522471964c3c8cf1e601770edcc3", "secret_name": "chittyauth_issued_mint_api_key" }, | ||
| { "binding": "COURTLISTENER_API_TOKEN", "store_id": "e914522471964c3c8cf1e601770edcc3", "secret_name": "COURTLISTENER_API_TOKEN" } |
| { "binding": "CHITTYAUTH_ISSUED_MINT_TOKEN", "store_id": "e914522471964c3c8cf1e601770edcc3", "secret_name": "chittyauth_issued_mint_api_key" }, | ||
| { "binding": "MINT_API_KEY", "store_id": "e914522471964c3c8cf1e601770edcc3", "secret_name": "chittyauth_issued_mint_api_key" } | ||
| { "binding": "MINT_API_KEY", "store_id": "e914522471964c3c8cf1e601770edcc3", "secret_name": "chittyauth_issued_mint_api_key" }, | ||
| { "binding": "COURTLISTENER_API_TOKEN", "store_id": "e914522471964c3c8cf1e601770edcc3", "secret_name": "COURTLISTENER_API_TOKEN" } |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b39b08ac1a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const hit = await cache.match(cacheReq); | ||
| if (hit) { | ||
| // Clone — Cache API responses are immutable | ||
| const cloned = new Response(hit.body, hit); | ||
| cloned.headers.set("X-Cache", "HIT"); | ||
| return cloned; |
There was a problem hiding this comment.
Check the token before serving cache hits
When the Cache API has a hit, this returns the cached CourtListener response before getCourtListenerToken() runs. If the token is removed, missing in a deployed environment, or the Secrets Store binding is temporarily unavailable after a previous successful request populated the cache, the documented fail-closed 503 POLICY_BLOCKED_CHITTYCONNECT_UNAVAILABLE path is bypassed for that URL and callers keep receiving cached data. Resolve the token before cache.match() or skip cache hits when token resolution fails.
Useful? React with 👍 / 👎.
| * The cache key is the absolute upstream URL; bypass via X-Bypass-Cache: 1. | ||
| */ | ||
| async function clGet(c, upstreamUrl) { | ||
| const bypass = c.req.header("X-Bypass-Cache") === "1"; |
There was a problem hiding this comment.
Add X-Bypass-Cache to CORS allowHeaders
This new bypass path depends on browser clients sending X-Bypass-Cache: 1, but the router's CORS middleware only allows Content-Type, auth, Chitty, and MCP headers, so requests from the allowed ChittyCounsel origin that include this header will fail preflight before reaching this route. Add X-Bypass-Cache to the CORS allowHeaders list or the documented bypass mechanism only works for non-browser callers.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@public/openapi.json`:
- Around line 1336-1339: OpenAPI currently omits the proxy's 502 error for
CourtListener endpoints; update the CourtListener operation responses in
public/openapi.json to include a 502 response (e.g., "description": "Bad Gateway
- upstream fetch failed or invalid JSON") for the same GET and POST proxy
operations described in src/api/routes/courtlistener.js (the proxy handlers that
perform upstream fetch/JSON parsing), ensuring each operation that lists
200/404/503 also lists 502 so the spec matches runtime behavior.
In `@src/api/routes/courtlistener.js`:
- Around line 77-82: getCredential is being passed an undeclared env fallback
("COURTLISTENER_API_TOKEN_FALLBACK") which reintroduces a third credential path;
remove that fallback argument so the call uses the broker/secrets-only
resolution. Locate the call to getCredential in courtlistener.js and change the
invocation to pass the env, the secret path
("integrations/courtlistener/api_token") and the service name ("CourtListener")
only (i.e., drop the fallbackEnvVar parameter), keeping the getCredential
signature usage consistent with credential-helper.js.
- Around line 107-123: The cached-response path returns a HIT before verifying
the CourtListener token, violating fail-closed; fix by obtaining and validating
the token (call getCourtListenerToken(c.env) and check !token with
failClosedNoToken) before returning any cached Response in the cache.match
branch (while preserving the existing try/catch and X-Cache header logic in the
cache handling code paths such as the cache hit branch and the functions
getCourtListenerToken and failClosedNoToken).
In `@tests/api/courtlistener-routes.test.js`:
- Around line 147-155: The test uses a loose length check
(expect(body.error.length).toBeLessThan(400)) which doesn't enforce the 200-char
truncation contract; update the assertions in the "propagates upstream non-2xx
status and truncates body to 200 chars" test to (1) assert the response contains
the first 200 characters of longBody
(expect(body.error).toContain(longBody.slice(0,200))), (2) find where that
200-char prefix appears (const prefixIndex =
body.error.indexOf(longBody.slice(0,200));
expect(prefixIndex).toBeGreaterThan(-1)), and (3) assert the total length is at
most that prefix position + 200
(expect(body.error.length).toBeLessThanOrEqual(prefixIndex + 200)), optionally
also assert the 201st char is not present
(expect(body.error).not.toContain(longBody.slice(200,201))). This replaces the
loose expect(body.error.length).toBeLessThan(400) check and uses longBody,
get("/search"...), and body.error to locate and verify truncation.
- Around line 163-170: Update the "never echoes the API token in error bodies"
test to also assert the actual token value is absent from the error string:
after getting body.error, add an assertion that it does not contain the token
from the test env (use the env token variable used when calling get, e.g.
env.COURT_LISTENER_TOKEN or fallback to the literal "test-cl-token" used in the
mock). Keep the existing Authorization check and ensure you reference the test's
get("/search", env, ...) call and body.error in the assertion.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: cf58de92-34cb-4823-a95e-84ceb5cb73e3
📒 Files selected for processing (5)
public/openapi.jsonsrc/api/router.jssrc/api/routes/courtlistener.jstests/api/courtlistener-routes.test.jswrangler.jsonc
| "responses": { | ||
| "200": { "description": "Search results", "content": { "application/json": { "schema": { "type": "object" } } } }, | ||
| "503": { "description": "CourtListener token not provisioned" } | ||
| } |
There was a problem hiding this comment.
Document the proxy’s 502 error path in the CourtListener operations.
src/api/routes/courtlistener.js returns 502 for upstream fetch failures and invalid JSON on both the GET and POST proxy paths, but these operations only advertise 200/404/503. That leaves /openapi.json out of sync with the real runtime contract.
Also applies to: 1351-1354, 1365-1369, 1380-1384
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@public/openapi.json` around lines 1336 - 1339, OpenAPI currently omits the
proxy's 502 error for CourtListener endpoints; update the CourtListener
operation responses in public/openapi.json to include a 502 response (e.g.,
"description": "Bad Gateway - upstream fetch failed or invalid JSON") for the
same GET and POST proxy operations described in src/api/routes/courtlistener.js
(the proxy handlers that perform upstream fetch/JSON parsing), ensuring each
operation that lists 200/404/503 also lists 502 so the spec matches runtime
behavior.
| return getCredential( | ||
| env, | ||
| "integrations/courtlistener/api_token", | ||
| "COURTLISTENER_API_TOKEN_FALLBACK", | ||
| "CourtListener", | ||
| ); |
There was a problem hiding this comment.
Remove the undeclared env fallback from token resolution.
src/lib/credential-helper.js falls back to env[fallbackEnvVar] when the broker misses, so passing "COURTLISTENER_API_TOKEN_FALLBACK" silently reopens a third credential path outside the Secrets Store / broker-only policy described for this proxy.
Suggested fix
return getCredential(
env,
"integrations/courtlistener/api_token",
- "COURTLISTENER_API_TOKEN_FALLBACK",
+ undefined,
"CourtListener",
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return getCredential( | |
| env, | |
| "integrations/courtlistener/api_token", | |
| "COURTLISTENER_API_TOKEN_FALLBACK", | |
| "CourtListener", | |
| ); | |
| return getCredential( | |
| env, | |
| "integrations/courtlistener/api_token", | |
| undefined, | |
| "CourtListener", | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/api/routes/courtlistener.js` around lines 77 - 82, getCredential is being
passed an undeclared env fallback ("COURTLISTENER_API_TOKEN_FALLBACK") which
reintroduces a third credential path; remove that fallback argument so the call
uses the broker/secrets-only resolution. Locate the call to getCredential in
courtlistener.js and change the invocation to pass the env, the secret path
("integrations/courtlistener/api_token") and the service name ("CourtListener")
only (i.e., drop the fallbackEnvVar parameter), keeping the getCredential
signature usage consistent with credential-helper.js.
| it("propagates upstream non-2xx status and truncates body to 200 chars", async () => { | ||
| const longBody = "x".repeat(500); | ||
| globalThis.fetch = vi.fn().mockResolvedValue(errorResponse(403, longBody)); | ||
| const res = await get("/search", env, { query: "q=foo" }); | ||
| expect(res.status).toBe(403); | ||
| const body = await res.json(); | ||
| expect(body.error).toContain("403"); | ||
| expect(body.error.length).toBeLessThan(400); | ||
| }); |
There was a problem hiding this comment.
Truncation assertions are too loose to enforce the 200-char contract.
Using length < 400 can still pass if truncation regresses well beyond 200 characters. Assert against the contract directly (e.g., max prefix + 200 body chars) so failures are deterministic.
Suggested tightening
it("propagates upstream non-2xx status and truncates body to 200 chars", async () => {
const longBody = "x".repeat(500);
globalThis.fetch = vi.fn().mockResolvedValue(errorResponse(403, longBody));
const res = await get("/search", env, { query: "q=foo" });
expect(res.status).toBe(403);
const body = await res.json();
expect(body.error).toContain("403");
- expect(body.error.length).toBeLessThan(400);
+ expect(body.error).toBe(`CourtListener API 403: ${"x".repeat(200)}`);
});
it("returns upstream error with truncation", async () => {
globalThis.fetch = vi.fn().mockResolvedValue(errorResponse(400, "y".repeat(500)));
const res = await post("/citation-lookup", env, { body: { text: "garbage" } });
expect(res.status).toBe(400);
const body = await res.json();
- expect(body.error.length).toBeLessThan(400);
+ expect(body.error).toBe(`CourtListener API 400: ${"y".repeat(200)}`);
});Also applies to: 272-278
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/api/courtlistener-routes.test.js` around lines 147 - 155, The test uses
a loose length check (expect(body.error.length).toBeLessThan(400)) which doesn't
enforce the 200-char truncation contract; update the assertions in the
"propagates upstream non-2xx status and truncates body to 200 chars" test to (1)
assert the response contains the first 200 characters of longBody
(expect(body.error).toContain(longBody.slice(0,200))), (2) find where that
200-char prefix appears (const prefixIndex =
body.error.indexOf(longBody.slice(0,200));
expect(prefixIndex).toBeGreaterThan(-1)), and (3) assert the total length is at
most that prefix position + 200
(expect(body.error.length).toBeLessThanOrEqual(prefixIndex + 200)), optionally
also assert the 201st char is not present
(expect(body.error).not.toContain(longBody.slice(200,201))). This replaces the
loose expect(body.error.length).toBeLessThan(400) check and uses longBody,
get("/search"...), and body.error to locate and verify truncation.
C1 — Drop `Cache-Control: public, max-age=900` from proxy responses.
The Worker Cache API stores responses internally and does not require
this header for HIT semantics. Emitting `public, max-age=…` would also
instruct downstream browsers/CDNs to cache responses from an
authenticated proxy. Matches google.js.
I1 — Reject unknown resources with `400 { error: "path_not_allowed",
resource }` per spec (was 404). Updated openapi.json accordingly:
generic-resource path now declares 400 with a typed error schema;
{resource}/{id} retains a separate 404 for upstream not-found.
I3 — Defense-in-depth: scrub the resolved API token from upstream
error bodies on both GET and POST paths before truncation. If
CourtListener ever reflected the Authorization header value back in
its error response, the token would no longer leak through the proxy.
I5 — Align getCredential(...) call signature with google.js:
(env, brokerPath, fallbackEnvVar, logPrefix). The fallback env var
name now matches the Secrets Store binding name (was a bogus
`_FALLBACK` suffix), so a single configured env value works whether
delivered as a Secrets Store binding or a plain Worker env var.
I6 — Pin upstream Content-Type to application/json on POST
/citation-lookup. CourtListener's citation-lookup endpoint only
accepts JSON; forwarding an arbitrary client-supplied Content-Type
would let a caller confuse the upstream parser.
Tests
-----
- Update existing 404-allowlist test to assert 400 path_not_allowed +
resource field.
- Strengthen "never echoes the API token" assertion to verify scrub
marker `[REDACTED]` and absence of the literal token string.
- Cache-miss test asserts Cache-Control header is now absent (was
asserting max-age=900).
- New: success-path body does not contain the token; CL 429 status
propagates verbatim; POST scrubs token in error body; POST pins
Content-Type to application/json regardless of client header;
POST never consults Cache API.
Deferred (not blocking):
- I2 (dead citation-lookup in GET allowlist): intentional — the
allowlist entry causes the generic GET route to match and emit
405 instead of 400 path_not_allowed, which is the correct method
signal for a wrong-verb call.
- I4 (cache key not tenant-scoped): intentional and documented inline
— CourtListener data is public Free Law Project content and
identical across tenants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Review-fix pass on AddressedC1 — Dropped I1 — Unknown resources now return I3 — Added I5 — Aligned I6 — Pinned upstream New tests (I7)
Deferred (not blocking — intentional)
ValidationBaseline was 463 passed (+6 new tests, all green). No deploy performed. |
|
Deploy deferred pending credential routing. Blocker tracked in #237 — Code/tests on this PR are merge-ready independently. Will redeploy and re-run |
Summary
Adds a CourtListener REST v4 proxy at
/api/proxy/courtlistener/*for ChittyCounsel (PR https://github.com/chittycorp/CHITTYCOUNSEL/pull/4). Mirrors the establishedgoogle.jsproxy pattern with CourtListener-specific behaviors per spec.Endpoints
GET /api/proxy/courtlistener/search— unified search, query passthroughGET /api/proxy/courtlistener/:resource[/:id]— allowlisted resource fetchPOST /api/proxy/courtlistener/citation-lookup— citation lookup, body forwarded verbatimAllowlist:
search, opinions, clusters, dockets, docket-entries, recap-documents, audio, citation-lookup, people, courts. Unknown resources → 404 before any upstream call.Spec compliance vs google.js (deltas)
Authorization: Token <key>(CL convention, not Bearer)503 POLICY_BLOCKED_CHITTYCONNECT_UNAVAILABLEwhen token absentX-Bypass-Cache: 1escape/citation-lookupbody forwardingCredential plane (synthetic env model)
Token is delivered via Cloudflare Secrets Store binding
COURTLISTENER_API_TOKENdeclared inwrangler.jsoncat:dev)env.staging.secrets_store_secretsenv.production.secrets_store_secretsAll three reference store
e914522471964c3c8cf1e601770edcc3.Status of the secret VALUE: NOT yet present in the store — verified via
npx wrangler secrets-store secret list e914522471964c3c8cf1e601770edcc3 --remote --per-page 100(no entry matchingcourt). This is a known provisioning gap that chico owns downstream. Until the value is written, the 503 fail-closed path is exercised — deploy of this PR is safe and non-disruptive (no behavior change to existing routes; new routes simply return 503 to consumers).No operator credential paste path. No
op://URI dependency.Tests
21 new unit tests in
tests/api/courtlistener-routes.test.js, mirroring the establishedgoogle-routes.test.jspattern (vi.mockoncredential-helper— repo-local convention for route unit tests; real service-integration smoke is the post-deploycurlevidence in the test plan below).Files
src/api/routes/courtlistener.js(new) — proxy implementationsrc/api/router.js— mounts at/api/proxy/courtlistenerwrangler.jsonc— 3secrets_store_secretsbinding entries (top + staging + production)public/openapi.json— 4 path entries underCourtListenertagtests/api/courtlistener-routes.test.js(new) — 21 testsTest plan
npx vitest run tests/api/courtlistener-routes.test.js— 21/21 passnpx vitest run tests/api/— 137/137 pass (no regressions)npx eslint src/api/routes/courtlistener.js tests/api/courtlistener-routes.test.js— cleanwrangler.jsoncparses (JSONC strip-comments + JSON.parse)public/openapi.jsonparsesnpm run deploy:staging/npm run deploy:production(safe-deploy)COURTLISTENER_API_TOKENvalue into Cloudflare Secrets Storee914522471964c3c8cf1e601770edcc3🤖 Generated with Claude Code
Summary by CodeRabbit
New Features