-
Notifications
You must be signed in to change notification settings - Fork 0
feat(console): route Indexer and search through CONSOLE_DATA_API_URL #136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,20 @@ | ||
| // SOURCING: none. Server-only GraphQL adapter for the Indexer projection | ||
| // (`topicIndexerObjects`). The browser never talks to Theorem directly. | ||
| // (`topicIndexerObjects`) over CONSOLE_DATA_API_URL. | ||
| // HANDOFF-CONSOLE-SINGLE-DOOR-1.0: no CONSOLE_HARNESS_* on this path. | ||
|
|
||
| import 'server-only'; | ||
|
|
||
| import type { JsonValue, ObjectRef } from '@commonplace/block-view/types'; | ||
| import { callHarnessGraphql } from '@/lib/server/harness-graphql'; | ||
| import { consumerGraphqlUrl } from '@/lib/server/consumer-graphql'; | ||
| import { startHarnessRequestTimeout } from '@/lib/server/harness-timeout'; | ||
| import { | ||
| principalTenantHeaders, | ||
| resolveHarnessPrincipal, | ||
| } from '@/lib/server/harness-principal'; | ||
| import { | ||
| credentialHeaders, | ||
| resolveUpstreamCredential, | ||
| } from '@/lib/server/upstream-credential'; | ||
|
|
||
| export type IndexerRead = | ||
| | { readonly ok: true; readonly tenant: string; readonly objects: readonly ObjectRef[] } | ||
|
|
@@ -40,6 +50,72 @@ function objectsFromPayload(data: Record<string, unknown>): ObjectRef[] { | |
| })); | ||
| } | ||
|
|
||
| async function executeConsumerGraphql( | ||
| query: string, | ||
| variables: Record<string, unknown>, | ||
| ): Promise< | ||
| | { readonly ok: true; readonly tenant: string; readonly data: Record<string, unknown> } | ||
| | { readonly ok: false; readonly status: number; readonly error: string } | ||
| > { | ||
| const resolution = await resolveHarnessPrincipal(); | ||
| if (!resolution.ok) { | ||
| return { | ||
| ok: false, | ||
| status: resolution.response.status, | ||
| error: 'principal_resolution=unauthenticated', | ||
| }; | ||
| } | ||
| const endpoint = consumerGraphqlUrl(); | ||
| if (!endpoint) return { ok: false, status: 404, error: 'indexer_graphql_unconfigured' }; | ||
|
|
||
| const credential = await resolveUpstreamCredential(resolution.principal); | ||
| if (!credential.ok) { | ||
| return { ok: false, status: 403, error: 'indexer_credential_unavailable' }; | ||
| } | ||
|
|
||
| const timeout = startHarnessRequestTimeout(); | ||
| try { | ||
| const upstream = await fetch(endpoint, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| ...credentialHeaders(credential.credential), | ||
| ...principalTenantHeaders(resolution.principal), | ||
| }, | ||
| body: JSON.stringify({ query, variables }), | ||
| cache: 'no-store', | ||
| signal: timeout.signal, | ||
| }); | ||
| const payload = await upstream.json().catch(() => null) as { | ||
| data?: Record<string, unknown>; | ||
| errors?: Array<{ message?: unknown }>; | ||
| } | null; | ||
| if (!upstream.ok || payload?.errors || !payload?.data) { | ||
| const detail = payload?.errors?.[0]?.message; | ||
| return { | ||
| ok: false, | ||
| status: upstream.ok ? 502 : upstream.status, | ||
| error: typeof detail === 'string' ? detail : indexerTransportError(upstream.status, timeout.didTimeout()), | ||
| }; | ||
|
Comment on lines
+93
to
+99
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Files:"
git ls-files | rg 'apps/console/src/lib/server/(indexer-harness|web-research)\.ts$' || true
echo
echo "indexer-harness outline:"
ast-grep outline apps/console/src/lib/server/indexer-harness.ts --view expanded || true
echo
echo "web-research outline:"
ast-grep outline apps/console/src/lib/server/web-research.ts --view expanded || true
echo
echo "Relevant slices:"
wc -l apps/console/src/lib//.
sed -n '1,140p' apps/console/src/lib/server/indexer-harness.ts
echo '--- web-research 1-170 ---'
sed -n '1,170p' apps/console/src/lib/server/web-research.tsRepository: Travis-Gilbert/CommonPlace Length of output: 1511 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- indexer-harness 93-130, 174-210 ---'
sed -n '93,130p' apps/console/src/lib/server/indexer-harness.ts
sed -n '174,210p' apps/console/src/lib/server/indexer-harness.ts
echo
echo '--- web-research 120-145 ---'
sed -n '120,145p' apps/console/src/lib/server/web-research.ts
echo
echo '--- GraphQL error message fields in consumer-graphql helpers ---'
rg -n "errors\\?\\.\\[0\\]\\.message|envelope\\?.*errors|GRAPHQL|THEOREM|CONSOLE_HARNESS|graphql_query" apps/console/src/lib/server apps/console/src -g '*.ts' -g '*.tsx' | head -200Repository: Travis-Gilbert/CommonPlace Length of output: 19733 Do not expose raw upstream GraphQL error messages. Both 📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| } | ||
|
Comment on lines
+93
to
+100
|
||
| return { ok: true, tenant: resolution.principal.tenant, data: payload.data }; | ||
| } catch { | ||
| return { | ||
| ok: false, | ||
| status: timeout.didTimeout() ? 504 : 502, | ||
| error: timeout.didTimeout() ? 'indexer_graphql_timeout' : 'indexer_graphql_unreachable', | ||
| }; | ||
| } finally { | ||
| timeout.clear(); | ||
| } | ||
| } | ||
|
|
||
| function indexerTransportError(status: number, timedOut: boolean): string { | ||
| if (timedOut) return 'indexer_graphql_timeout'; | ||
| if (status === 404) return 'indexer_graphql_unconfigured'; | ||
| return 'indexer_graphql_failed'; | ||
| } | ||
|
Comment on lines
+113
to
+117
|
||
|
|
||
| const PREVIEW_IMAGE_CONTENT_TYPES = new Set([ | ||
| 'image/png', | ||
| 'image/jpeg', | ||
|
|
@@ -63,6 +139,9 @@ export async function readIndexerPreviewAsset(assetId: string): Promise< | |
| return { ok: false, status: 400, error: 'invalid_preview_asset_id' }; | ||
| } | ||
|
|
||
| // Preview assets remain on the agent GraphQL surface until commonplace-api | ||
| // mounts topicPreviewAsset; Indexer object reads already use the data door. | ||
| const { callHarnessGraphql } = await import('@/lib/server/harness-graphql'); | ||
| const result = await callHarnessGraphql( | ||
|
Comment on lines
+142
to
145
|
||
| ` | ||
| query ConsoleIndexerPreview($assetId: String!) { | ||
|
|
@@ -96,20 +175,20 @@ export async function readIndexerObjects(options: { | |
| readonly topicId?: string; | ||
| readonly includeCaptures?: boolean; | ||
| }): Promise<IndexerRead> { | ||
| const result = await callHarnessGraphql(INDEXER_OBJECTS_QUERY, { | ||
| const result = await executeConsumerGraphql(INDEXER_OBJECTS_QUERY, { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This switches Indexer reads to the consumer schema, but a repo-wide search of this commit's AGENTS.md reference: AGENTS.md:L27-L27 Useful? React with 👍 / 👎. |
||
| topicId: options.topicId ?? null, | ||
| includeCaptures: options.includeCaptures ?? Boolean(options.topicId), | ||
| }); | ||
| if (!result.ok) { | ||
| return { | ||
| ok: false, | ||
| status: result.status, | ||
| error: indexerError(result.error), | ||
| error: result.error, | ||
| }; | ||
| } | ||
| return { | ||
| ok: true, | ||
| tenant: result.principal.tenant, | ||
| tenant: result.tenant, | ||
| objects: objectsFromPayload(result.data), | ||
| }; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
CONSOLE_DATA_API_URLis configured, this redirects everyconsumerGraphqlUrl()caller to commonplace-api, butproactivity-harness.tsandfiling-harness.tsstill authenticate exclusively withTHEOREM_API_KEYrather thanCONSOLE_DATA_API_KEY. In the documented Railway configuration where the data API has its own key, both existing surfaces will receive 403 responses; either scope the new endpoint selection to Indexer/search or migrate those callers' credentials simultaneously.AGENTS.md reference: apps/console/AGENTS.md:L33-L36
Useful? React with 👍 / 👎.