Skip to content

feat(history): consolidate durable costs and asset previews - #78

Merged
eliteprox merged 27 commits into
mainfrom
codex/history-cost-assets
Sep 11, 2026
Merged

feat(history): consolidate durable costs and asset previews#78
eliteprox merged 27 commits into
mainfrom
codex/history-cost-assets

Conversation

@peacenode

@peacenode peacenode commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

History now joins runs to PymtHouse billing through captured payment manifests, so a billing event ID such as 4096f6f3 does not have to equal Console's job_* ID. Costs come from PymtHouse's existing per-manifest usage aggregates and are persisted in Neon for the same table/drawer presentation. This replaces the ten-page raw-receipt scan and model/time guessing.

This PR also consolidates #75 and #77's History detail, first-party asset previews, durable asset lineage, and Base UI Dialog/Tooltip work.

Billing behavior

  • Pinned @pymthouse/gateway-web@0.3.5 (published 2026-09-11, v0.3.5) is the validated payment-observer artifact. Upstream now ships the awaited onPayment callback in ESM and CommonJS across single-shot, persistent, and failover paths, replacing the earlier local pnpm patch on 0.3.4. Console saves the payment manifest before charging and marks acceptance before awaiting provider completion. Persistence failures stop without triggering another paid attempt.
  • run_payment_manifests enforces one run per account-scoped manifest and stores replacement-based cumulative usage snapshots. Multiple paid attempts are summed once; repeat or stale refreshes cannot double-count or overwrite newer observations.
  • Billing sync uses the authenticated /api/v1/user/usage?groupBy=manifest endpoint over the relevant date range, including month boundaries. It matches exact captured manifests and refreshes while usage is pending. It does not scan unrelated receipt pages.
  • Raw receipts remain separate evidence. Historical/preview runs without captured manifests can retain previously persisted exact receipt totals; no manifest or cost is inferred from model/time. Missing aggregates for accepted attempts do not display a partial total as complete.
  • History remains readable during billing outages. Both table and drawer use the same persisted network-cost summary. Usage refresh is eventually consistent, not invoice finality.

Assets and UI

  • Owner-bound signed first-party playback URLs with origin/redirect validation and provider expiry enforcement.
  • Durable input/output asset lineage, owned input validation, and provider URL removal from public execution/detail payloads.
  • Base UI Dialog and registry-aligned Tooltip components, with focus management, Escape behavior, layering and padding fixes.
  • Provider schema loading runs after the initial detail request.

Live verification

An instrumented paid Flux Schnell request captured manifest bdcd8fdd at payment time. The authenticated PymtHouse aggregate and filtered receipt both returned bdcd8fdd, with aggregate network cost 2982 USD micros ($0.002982). The receipt's gateway/event ID was 4096f6f3, different from the caller-supplied job_manifest_probe_581020f9de434292.

This verifies the manifest join for a successful single-shot run. SDK contracts cover persistent execution, paid failover, and persistence failures; transactional integration tests cover owner isolation, deduplication, replacement snapshots, stale responses, and table/detail parity.

Deployment prerequisites

  • Preview has 0001_run_observability_and_lineage.sql and 0002_run_payment_manifests.sql applied, with runtime grants verified on 2026-09-11. Production must apply both pending migrations before serving this revision: SELECT/INSERT on run_usage_receipts and run_asset_links, and SELECT/INSERT/UPDATE on run_payment_manifests. No additional migration is needed for the review fixes.
  • Keep ASSET_URL_SIGNING_SECRET and ASSET_PROXY_ALLOWED_HOSTS configured.
  • No PymtHouse backend ID-propagation change is required for this integration.

Migration 0002 was rehearsed and applied to the isolated runtime preview; production has not been migrated by this work. Database regression tests run only on the marked disposable Neon integration branch and clean up their test schemas/data. The existing manual preview at https://console-history-cost-assets.vercel.app predates this manifest amendment; its fixture rows demonstrate the earlier History/assets work, not the new callback path.

Copilot review fixes

  • Pin validated DNS addresses for each proxy HTTPS connection and redirect, preserving TLS hostname checks, streaming, HEAD/range behavior, timeouts and cancellation.
  • Reject unavailable/expired media delivery while retaining History records, lineage, parameters and cost. Unknown expiry stays playable; successful caching is capped at 60 seconds and bounded by signature/expiry, and errors are not cached. availableUntil remains an availability guarantee, distinct from hard expiresAt.
  • Sanitize input/output media independently of asset extraction, including masks/references, redacted signed URLs, partial asset persistence, and both account-request paths. Only owned media gets first-party playback URLs.
  • Propagate explicit output expiry through execution/reconciliation and preserve it during upserts.
  • Serialize preview fixtures per owner in one transaction, skip complete fixtures, and repair partial records without weakening ordinary terminal-run protection.
  • Validate receipt numeric precision/scale; fix keyboard cost tooltips, input/output labels, local/preview asset references, local media origins and mock billing parity.
  • The missing-migrations finding is stale: registered migrations 0001 and 0002 exist. The alleged broken fixture-route mock was also stale: the pre-change test passed both cases; the updated test exercises orchestration through the new transaction boundary.

Follow-up findings addressed

  • Billing refresh considers all requested owned runs, including active runs without manifests; cached accepted manifests with missing fees remain pending.
  • Preview legacy rows are excluded before SQL pagination and totals. Opaque cursors preserve database microseconds, preventing skipped rows created in the same transaction.
  • Capture 3D model/texture/preview outputs, sanitize unsupported media schemes, and prefer renderable previews in the drawer.
  • All declared dev-mock media has a local response; synthetic WAV/GLB fixtures are bundled, and unknown mock IDs never reach a real database route.
  • Owner-bound preview fixture images redirect to fixed bundled image paths after signature and availability checks, without broadening the provider host allowlist.

Validation

  • Full suite: 488 passed, 50 skipped (database tests require explicit disposable-target configuration).
  • Console suite: 147 passed.
  • Disposable Neon integration: both run-store and preview concurrency/repair tests passed, including expiry upsert preservation and pagination/count regressions; test data cleaned up.
  • Local TLS-server test: pinned address, hostname verification, range/HEAD delivery and stream cancellation passed.
  • Typecheck, lint and production build passed.
  • Local browser with development mocks: image loaded from the same local origin, table/drawer costs matched, and missing-media run details remained visible. Keyboard tooltip behavior and unavailable-asset preservation also have automated UI coverage.
  • Earlier instrumented paid request verified the captured payment manifest against PymtHouse's returned aggregate. This is not a claim of a deployed Console paid end-to-end test.

Local build warnings concern missing local Auth0 configuration and the existing Edge runtime deprecation. CI will rerun on the updated head; no new CI result is claimed here.

Supersedes #75 and #77. Neither original PR is closed by this update.

@vercel

vercel Bot commented Sep 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
console Ready Ready Preview Sep 11, 2026 7:21pm UTC

Request Review

@peacenode

Copy link
Copy Markdown
Collaborator Author

Manual branch preview is ready: https://console-psra6vzuc-livepeer-foundation.vercel.app

The branch-scoped ASSET_URL_SIGNING_SECRET and ASSET_PROXY_ALLOWED_HOSTS settings are configured. Vercel build and deployment completed successfully; the original automatic Vercel check remains marked canceled from the dashboard, while Validate Console and CodeQL are green.

The paid end-to-end cost smoke test remains gated on PymtHouse returning the caller-supplied job_* correlation ID.

@peacenode

Copy link
Copy Markdown
Collaborator Author

Pulled the existing trial-observability pieces together in 65d1bd9:

  • normalized run_usage_receipts is now the authoritative queryable PymtHouse cost projection, with matching append-only run_events chronology
  • run_asset_links now records durable input/output lineage using the existing stable mcp_assets IDs
  • first-party input references are owner-checked at submission and retain role, parameter path, and ordinal
  • added a Vercel-preview-only authenticated fixture writer so each reviewer can create representative Neon-backed History records (single receipt, decimal-safe multi-receipt aggregate, unmatched receipt, generated asset, and reused input)

Local test, Console test, lint, typecheck, and production build are green. The additive migration rollback rehearsal correctly stopped because the available preview DATABASE_URL is the least-privilege runtime role. Before redeploying this head, the approved Neon owner connection must apply drizzle-baseline/0001_run_observability_and_lineage.sql and grant the runtime role SELECT/INSERT on run_usage_receipts and run_asset_links. No production database was queried or changed.

@peacenode

Copy link
Copy Markdown
Collaborator Author

Preview verification is ready at https://console-history-cost-assets.vercel.app

After signing in, open Calls and choose Create preview verification records. It creates three records scoped to your account: a single receipt, a two-receipt aggregate with asset reuse, and an unmatched/failed run showing . Please verify table/drawer cost parity and input/output assets in the drawer.

The preview database migration and least-privilege runtime access have been verified. Smoke checks pass, and all GitHub Actions checks are green. The repository Vercel check is still marked failed only because its automatic deployment was canceled from the Vercel Dashboard; the linked manual preview built successfully from 65d1bd9.

@peacenode

Copy link
Copy Markdown
Collaborator Author

Updated preview behavior at https://console-history-cost-assets.vercel.app

No setup button is required now. On the first authenticated History read, the preview automatically provisions an idempotent, account-scoped dataset and returns a matching preview usage balance. The visible dataset contains four richer records covering image generation, asset reuse, text inference, and a provider failure. Run drawers include stored media, prompts, parameters, provider results/timing, costs, receipts, and input/output lineage.

Superseded button-era fixture rows are hidden in preview responses without deleting Neon data. Verified in the deployed UI: $5 preview balance, four History rows, and a Flux Schnell drawer with image, prompt, seed, image size, safety setting, $0.0100 cost, and 1.28s render status. This supersedes my earlier instruction to click Create preview verification records.

@peacenode
peacenode marked this pull request as ready for review September 10, 2026 20:54
@peacenode
peacenode requested review from eliteprox and a lite review from Copilot September 10, 2026 20:54
@peacenode

Copy link
Copy Markdown
Collaborator Author

@eliteprox this is ready for your approval. It folds #75 and #77 together and now has a reviewer-ready Neon-backed preview at https://console-history-cost-assets.vercel.app

The preview auto-provisions four account-scoped History runs, so there is no setup button or paid request required. The main review points are:

  • table/drawer Cost parity from persisted Neon receipts
  • multiple-receipt aggregation and unmatched behavior
  • durable input/output asset lineage and signed playback URLs
  • Base UI Dialog/Tooltip behavior, including the latest tooltip layering, top alignment, and padding fixes

Validate Console and CodeQL are green. The stable preview is built from current head 2b49f85. The red repository Vercel status is the canceled automatic deployment; the manual deployment at the stable URL succeeded.

The remaining release prerequisite is still the downstream PymtHouse correction that returns the Console-supplied job_* correlation ID end to end; that should block production release, not review of this PR implementation.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The billing sync endpoint has a confirmed loop-control/performance defect (matched IDs are never removed from the wanted set), and there’s an easy type-safety fix that should be applied before merging.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR consolidates run History cost computation and first-party asset preview handling by persisting authoritative billing receipts in Neon, deriving a single billing summary for both the History table and detail drawer, and proxying asset playback through owner-bound signed URLs. It also replaces the legacy tooltip implementation/Radix usage with registry-aligned Base UI primitives and moves provider schema loading off the run-detail critical path.

Changes:

  • Persist append-only billing receipts (run_usage_receipts) and surface a Neon-derived billing summary (billing) in run list + detail flows.
  • Introduce first-party asset signing + proxying (/api/assets/:id) and rewrite captured run payloads to remove provider media URLs.
  • Add preview-only fixture seeding and UI/contract coverage for history, billing sync, asset security, and tooltip/dialog primitives.
File summaries
File Description
tests/integration/run-records.test.ts Extends integration coverage for durable asset lineage (run_asset_links) and receipt aggregation into billing.
tests/integration/mcp-assets.test.ts Updates asset serialization expectations to use first-party signed URLs.
tests/contracts/ui-primitive-dependencies.test.ts Adds contract test preventing Radix dependency/import reintroduction in UI primitives.
tests/contracts/session-allowance.test.tsx Verifies preview usage can be served without contacting PymtHouse when preview fixtures are enabled.
tests/contracts/run-security.test.ts Expands redaction/output extraction contract to cover *_urls array outputs.
tests/contracts/run-http.test.ts Verifies preview fixture seeding occurs before listing runs and legacy preview rows are filtered.
tests/contracts/run-execution.test.ts Ensures durable execution returns first-party asset URLs and does not leak provider media URLs/status URLs.
tests/contracts/preview-run-fixtures-route.test.ts Adds contract coverage for preview-only fixture creation endpoint and seeded receipt lineage.
tests/contracts/home-history-surface.test.tsx Updates History surface tests for tooltip provider usage and Neon-derived cost behavior.
tests/contracts/call-detail-media.test.tsx Adds detailed UI contract coverage for media stage behavior, expiry, tooltips, and captured field presentation.
tests/contracts/billing-sync-route.test.ts Adds contract tests for bounded, exact-match billing sync and run-id limits.
tests/contracts/asset-proxy.test.ts Adds security contract tests for signed asset proxying (range requests, allowlist, private IP refusal, redirect validation).
tests/contracts/admin-runs-preview.test.tsx Updates admin drawer expectations to present captured fields without raw JSON section labels.
tests/contracts/account-history.test.ts Ensures cost lookups omit year window to preserve current-month ticket feed correctness.
tests/contracts/account-history-route.test.ts Updates route contract for new receipt metadata fields and recordRunUsage return type.
lib/runs/types.ts Introduces billing summary types, input schema types, and extends asset shape with role/displayName.
lib/runs/store.ts Implements receipt persistence + aggregation, asset lineage linking, and list/detail billing attachment.
lib/runs/preview-fixtures.ts Adds preview-only deterministic seeding of owner-scoped fixture runs + receipts and synthetic account usage payload.
lib/runs/outputs.ts Extends output extraction to include image_urls, video_urls, audio_urls.
lib/runs/execute.ts Rewrites execution payload to return first-party asset URLs and strips provider URL leakage from returned data.
lib/runs/billing.ts Adds decimal-safe aggregation helpers for receipt totals and billing summaries.
lib/runs/billing.test.ts Validates decimal-safe arithmetic and receipt aggregation behavior.
lib/mcp/store.ts Switches asset serialization to first-party signed URLs and adds getAssetSource for proxying.
lib/mcp/mcp-server.ts Updates MCP asset listing serialization to include principal-bound signed URLs.
lib/mcp/fal-input-schema.ts Adds schema parsing + caching for Fal OpenAPI input metadata to support History UI help.
lib/mcp/fal-input-schema.test.ts Tests Fal schema parsing and capability resolution behavior.
lib/db/schema/runs.ts Adds run_usage_receipts table for normalized, queryable billing evidence.
lib/db/schema/mcp-assets.ts Adds run_asset_links table for durable input/output lineage edges.
lib/db/full-baseline.test.ts Updates baseline/journal expectations to include additive migration(s).
lib/console/useRunHistory.ts Loads run input schema asynchronously after detail fetch to remove it from critical path.
lib/console/usage-capability-display.ts Improves model display names by stripping provider/catalog namespaces.
lib/console/types.ts Adds capabilityId to activity rows for pricing/joining context.
lib/console/signed-ticket-activity.ts Maps signed tickets to activity rows with capabilityId set for consistent joins.
lib/console/run-activity.ts Simplifies cost mapping to rely on persisted billing summary instead of inferred events/feeds.
lib/console/run-activity.test.ts Updates tests to reflect billing summary-driven cost display, including multi-receipt aggregation.
lib/console/pymthouse-bff.ts Adds recentWindow option to avoid from/to windows that drop current-month pricing tickets.
lib/console/dev-mock.ts Adds dev-mock run history/detail fixtures, schema mocks, and local /api/assets/* redirects for previews.
lib/console/capability-modality.ts Adds capabilityPresentation and modality labels to improve user-facing titles.
lib/console/capability-modality.test.ts Adds tests for capability title cleanup and namespace-stripped model display names.
lib/console/billing-receipts.ts Centralizes and tightens sanitization of upstream billing receipt fields before persistence.
lib/console/activity-output-match.ts Removes fuzzy/orchestrator-id inference; only exact gateway correlation is treated as authoritative.
lib/console/activity-output-match.test.ts Updates tests to ensure non-exact tickets do not attach assets.
lib/console/activity-assets.ts Rewrites attached output URLs to use first-party signed asset URLs.
lib/assets/public.ts Implements asset URL signing, captured payload rewriting, and provider URL stripping for public run detail responses.
lib/assets/public.test.ts Tests first-party URL rewriting and provider URL redaction behavior.
drizzle-baseline/meta/_journal.json Adds new migration entry to the baseline journal.
drizzle-baseline/0001_run_observability_and_lineage.sql Adds schema migration creating run_asset_links and run_usage_receipts with indexes/constraints.
docs/early-access/run-records.md Updates documentation to describe normalized receipts, sync bounds, lineage tables, and signed asset proxy behavior.
components/ui/tooltip.tsx Adds Base UI tooltip wrapper components used across the app.
components/design-system/Tooltip.tsx Removes legacy bespoke tooltip implementation.
components/console/ModalityChip.tsx Introduces shared ModalityChip used by table and drawer.
components/console/CallsTable.tsx Migrates tooltips to Base UI and adopts ModalityChip for consistent modality styling.
components/console/CallsSection.tsx Adds bounded authenticated billing sync for visible runs while keeping History resilient to billing outages.
components/console/CallDetailDrawer.tsx Migrates to Base UI dialog + tooltips, improves media stage behavior, and presents captured fields without raw JSON sections.
components/admin/RunsPreview.tsx Configures drawer variant for admin presentation mode.
app/layout.tsx Wraps the app in TooltipProvider for consistent tooltip behavior.
app/api/pymthouse/account-usage/route.ts Serves preview usage payload when preview fixtures are enabled.
app/api/pymthouse/account-requests/route.ts Uses receipt sanitization helper and enables recent-window behavior for correlation mode.
app/api/console/runs/route.ts Seeds preview fixtures (preview-only) before listing and filters legacy preview records.
app/api/console/runs/preview-fixtures/route.ts Adds preview-only authenticated endpoint to seed fixtures.
app/api/console/runs/billing-sync/route.ts Adds bounded, exact-match billing sync endpoint for visible run IDs.
app/api/console/runs/[id]/schema/route.ts Adds endpoint for loading run input schema metadata (Fal) off the detail critical path.
app/api/console/runs/[id]/route.ts Returns publicRunDetail to ensure asset URLs are signed and provider URLs are removed.
app/api/assets/[id]/route.ts Adds signed asset proxy route with origin/redirect validation and bounded caching.
app/api/admin/runs/[id]/route.ts Applies publicRunDetail for admin run detail responses as well.
.env.example Documents required asset signing/proxy env vars and preview fixture toggles.
Review details
  • Files reviewed: 66/67 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread app/api/console/runs/billing-sync/route.ts Outdated
Comment thread app/api/console/runs/billing-sync/route.ts Outdated
Copilot AI review requested due to automatic review settings September 11, 2026 17:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

One or more issues must be addressed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (4)

components/console/CallDetailDrawer.tsx:1142

  • The admin asset list renders every asset ID as a button, including inputs, but this lookup searches only outputAssets. Clicking an input therefore updates selectedAsset and immediately falls back to the default output, making the button ineffective for keyboard and pointer users. Resolve the selected ID against activeDetail.assets (or render input IDs as non-buttons).
    outputAssets?.find(
      (item) =>
        selectedAsset &&
        selectedAsset.runId === row?.id &&
        item.id === selectedAsset.id

components/console/CallDetailDrawer.tsx:1143

  • This only prefers the first renderable image/video/audio, not the actual 3D preview. detail() orders assets by createdAt, id, while transitionRun() inserts model, preview, and texture assets in one transaction, so the texture can sort before preview_image and be rendered as the drawer's main image. Preserve the output ordinal/preview role in the detail payload and select the preview before texture assets.
  const asset =
    outputAssets?.find(
      (item) =>
        selectedAsset &&
        selectedAsset.runId === row?.id &&
        item.id === selectedAsset.id
    ) ?? outputAssets?.find((item) => /^(?:image|video|audio)(?:\/|$)/i.test(item.mediaType ?? "")) ?? outputAssets?.[0];

components/console/CallsSection.tsx:35

  • The component accepts a request that is treated as either a run ID or a gatewayRequestId in found below, but this hook always fetches /api/console/runs/${requestId} and that route looks up only runs.id. A /home?request=job_* deep link therefore finds the row but the detail request returns 404. Resolve the gateway ID to the run ID before calling the hook, or make the detail route support the same lookup.
  const detail = useRunDetail(
    "/api/console/runs",
    requestId,
    ownerKey,
    isConnected

lib/runs/store.ts:198

  • runAssetLinks is the canonical lineage source, but this fallback overrides an explicit output link whenever the asset's producer runId differs from the current run (or is null for legacy assets). Such an asset is returned as role: "input", so it disappears from outputAssets and cannot be previewed. Honor the link direction whenever a link exists, and use runId only for unlinked legacy records.
  • Files reviewed: 86/91 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread lib/assets/public.ts
…ty sync path (#83)

* refactor(auth): land signed-in users without the identity sync route

Replace `identitySyncPath` and the deprecated `/api/identity/sync` route with direct `/home` redirects and a shared `signedInLandingPath` that chooses `/admin`, `/home`, or `/access-pending` from the signed-in identity and access decision.

Also abort in-flight History pagination when reloading so “Load older history” cannot stay blocked, and include a deep-linked `?request=` run in billing-sync even when it is off the first page.
- Introduced a new `wrapperKey` function to identify and strip provider URLs under generic keys (output, data, result) during media sanitization.
- Added tests to verify the correct stripping of provider URLs when wrapped in these keys, ensuring sensitive information is not exposed in public responses.
eliteprox
eliteprox previously approved these changes Sep 11, 2026
@eliteprox

Copy link
Copy Markdown
Collaborator

I think ready to merge, but I also am not sure this polling mechanism is the right way to get the usage into the db

/api/console/runs/billing-sync polls pymthouse all day long every 5 seconds and runs your neondb cpu up persistently
It's not really what we had a few days ago with the trace, which imo should happen all within the job itself. It works though

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Critical billing-validation and expiry-handling issues, plus related correctness and accessibility fixes, remain unresolved.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (10)

Previously missed (1) — in code that hasn't changed since the last review.

components/console/CallDetailDrawer.tsx:1529

  • The admin detail renders the internal errorCode directly as user-facing text. Codes such as provider_capacity and execution_outcome_unknown are implementation identifiers rather than helpful render status copy; map them to a stable human-readable label and keep the raw code only in an operator/debug affordance.

app/api/console/runs/billing-sync/route.ts:22

  • A null JSON body dereferences body.runIds, and malformed JSON also escapes as a generic exception; runError therefore returns 503 instead of the documented 400 invalid_run_query response for malformed client input. Parse into unknown, validate that it is a non-null object with an array of IDs, and translate JSON parse failures to invalid_run_query.
    const body = (await request.json()) as { runIds?: unknown };
    if (
      !Array.isArray(body.runIds) ||
      body.runIds.length > 50 ||
      body.runIds.some((id) => typeof id !== "string" || !id || id.length > 160)
    )
      throw new Error("invalid_run_query");

components/console/CallDetailDrawer.tsx:192

  • The video element exposes native controls but no accessible name (aria-label, title, or an associated label), so assistive-technology users cannot identify which preview they are controlling. Pass the media title into this preview and use it as the accessible name.
      <video
        src={src}
        controls={loaded}
        playsInline
        onCanPlay={() => setLoaded(true)}

components/console/CallDetailDrawer.tsx:227

  • The audio element exposes native controls but no accessible name (aria-label, title, or an associated label), so assistive-technology users cannot identify which preview they are controlling. Pass the media title into this preview and use it as the accessible name.
      <audio
        src={src}
        controls={loaded}
        onCanPlay={() => setLoaded(true)}

components/console/CallsSection.tsx:43

  • requestId is passed unchanged as both a durable run ID for useRunDetail and as a billing-sync runIds entry, but the matching logic below also accepts gatewayRequestId. A legacy /home?request=<gatewayRequestId> therefore requests /api/console/runs/<gateway-id> and never refreshes billing, while getOwnRun only looks up runs.id; normalize gateway IDs to the matched run ID or add an owner-scoped gateway lookup before loading the detail.
    const ids = history.page?.items.map((run) => run.id) ?? [];
    return requestId
      ? [requestId, ...ids.filter((id) => id !== requestId)]
      : ids;

components/console/CallsTable.tsx:279

  • The shared table always links a row to /home?request=<row.id>, but the app Jobs tab supplies MOCK_RECENT_REQUESTS rows whose IDs are req-*, not durable Neon run IDs. Clicking any Jobs row therefore leaves the app page and opens a History detail request that returns run_not_found; provide an app-specific navigation/selection handler or disable the History link for those rows.
                href={`/home?request=${row.id}`}

lib/console/session-user.ts:64

  • This token acquisition runs inside the shared admission path used by unrelated pages and APIs such as the session profile, device approval, admin checks, and run history. A cold start or token refresh now makes each of those requests await a PymtHouse network call; failures are then swallowed, so the added latency does not even guarantee a usable token. Keep end-user token minting lazy in the BFF consumers that actually need it, or explicitly separate account provisioning from request admission.
    lib/identity/signed-in-landing.ts:17
  • When the identity is pending, this redirects to /access-pending without carrying returnTo. app/access-pending/page.tsx uses that query to redirect an approved user, so a user who signed in for /keys, /usage, or another protected page is sent to the default /home after approval. Preserve the validated return path in the pending URL, as requireConsolePage does.
    lib/runs/preview-fixtures.ts:315
  • This fixture introduces a fractional endUserBillableUsdMicros value, but the shared microsToUsd helper currently calls BigInt and returns zero for fractional micros, so Usage/Sidebar/consumption will display this valid fixture as no spend. Update the conversion helper to handle the decimal precision before relying on fractional fixture values.
    lib/runs/store.ts:1189
  • The fixture is considered complete once the dispatch event, receipts, and (where applicable) assets exist, but successful fixtures never require runs.result to be non-null. A partial successful row can therefore skip the repair path and leave the detail drawer with no captured result; include the result (and repair the missing value) in the successful-fixture completeness contract.
  • Files reviewed: 102/107 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread lib/runs/store.ts
Comment thread lib/runs/store.ts
Copilot AI review requested due to automatic review settings September 11, 2026 18:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

One critical, four moderate, and one nit finding remain unresolved.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (5)

components/console/CallDetailDrawer.tsx:952

  • When a run has an internal errorCode but no errorMessage (for example queue_unavailable or execution_outcome_unknown), this renders the snake_case identifier directly in the user-facing drawer. Use a human-readable fallback (or a reviewed code-to-message map) instead of exposing the storage/API code.
            {errorMessage ?? errorCode}

components/console/CallDetailDrawer.tsx:1532

  • The admin drawer also exposes the internal errorCode before the optional message, so values such as recovery_horizon_exceeded become user-visible implementation details. Render the reviewed error message when present and a generic human-readable fallback otherwise; the code can remain in server-side logs.
                            {activeDetail.errorCode}
                            {activeDetail.errorMessage
                              ? `: ${activeDetail.errorMessage}`
                              : ""}

components/console/CallDetailDrawer.tsx:1532

  • FIELD_HELP is keyed by the rendered label, but this new field is labeled Cost while the matcher still only recognizes Price. As a result, the network-cost help tooltip is never attached, so keyboard users and anyone relying on the detail help lose the explanation for this value. Include Cost in the matcher (or pass the same canonical field name to both places).
                            {activeDetail.errorCode}
                            {activeDetail.errorMessage
                              ? `: ${activeDetail.errorMessage}`
                              : ""}

components/console/CallsSection.tsx:45

  • useRunHistory appends older pages to history.page.items, but slicing here permanently excludes every row loaded after the first page. Because the billing-sync endpoint is called only for these IDs, accepted manifests for older visible rows never refresh and their table/detail costs can remain stale or . Keep the endpoint's 50-ID limit, but batch all loaded IDs (or queue newly appended IDs) so pagination is eventually consistent.
  const visibleRunKey = visibleRunIds.slice(0, 50).join(",");

components/console/CallsSection.tsx:48

  • The ref is cleared while a selected detail is still loading, so billing sync can finish first and skip the detailReloadRef call at the sync handler. In that race the table updates but the open drawer keeps its stale cost until it is reopened; retain the selected request id as the fallback while detail.detail is null.
  openDetailIdRef.current = detail.detail?.id ?? null;
  • Files reviewed: 102/107 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread lib/assets/public.ts
…ing sync process

- Introduced `refreshOwnedRunBilling` and `refreshOwnedRunBillingByJob` functions to handle billing updates for owned runs, ensuring accurate fee tracking and manifest usage.
- Updated the billing sync route to utilize the new billing refresh logic, improving response handling and reducing unnecessary polling.
- Removed the setTimeout polling mechanism in the CallsSection component to prevent redundant requests when usage is pending.
- Enhanced tests to cover new billing refresh functionality and ensure correct behavior during billing sync operations.
- Introduced a new function, `publicErrorMessage`, to strip provider URLs from error messages and return null for non-string values.
- Updated the `publicRunDetail` function to utilize `publicErrorMessage`, ensuring sensitive information is not exposed in public responses.
- Added tests to verify the correct sanitization of error messages, confirming that provider URLs are removed and empty messages are handled appropriately.
@eliteprox
eliteprox merged commit 009a703 into main Sep 11, 2026
5 checks passed
@eliteprox
eliteprox deleted the codex/history-cost-assets branch September 11, 2026 19:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Seven unresolved moderate findings remain in media preview behavior, billing synchronization and acceptance, reload cancellation, and asset lineage.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (7)

components/console/CallDetailDrawer.tsx:1137

  • The admin asset list renders input assets as selectable buttons, but this lookup searches only outputAssets. Clicking an input therefore can never change the preview or set its aria-pressed state; search all assets for an explicit selection, then use output assets only for the default preview choice (or make input rows non-selectable).
  const outputAssets = activeDetail?.assets.filter(
    (item) => item.role !== "input"
  );
  const asset =

components/console/CallDetailDrawer.tsx:1222

  • The wheel handler exempts only [data-detail-scroll], so wheel events over the media stage fall through to preventDefault() and history navigation. Because MediaStage is itself overflow-auto, large images/JSON cannot be scrolled and can unexpectedly switch to the adjacent run; mark the media stage as an exempt detail surface (or otherwise limit navigation to the surrounding chrome).
      window.matchMedia("(max-width: 1023px)").matches ||
      isWithinDetailPanel(event.target)
    ) {

components/console/CallDetailDrawer.tsx:1143

  • The default preview is chosen by media type alone, so when the first image/video/audio output is already expired or marked unavailable, the drawer selects it and MediaStage shows “Media unavailable” even if a later output is still playable. Filter the default candidate by unavailableAt and a valid future expiresAt (while preserving an explicitly selected asset as a fallback) so the drawer actually prefers a renderable preview.
    ) ?? outputAssets?.find((item) => /^(?:image|video|audio)(?:\/|$)/i.test(item.mediaType ?? "")) ?? outputAssets?.[0];

components/console/CallsSection.tsx:71

  • If the billing-sync request resolves before the initial detail request, openDetailIdRef is still null, so this branch reloads the table but never reloads the open drawer. The drawer can therefore keep showing (or an old cost) while the table has the newly persisted manifest cost. Track the changed run IDs until the detail has loaded, or trigger the detail reload from the active request ID/detail effect rather than relying only on this ref snapshot.
          const openId = openDetailIdRef.current;
          if (openId && result.changedRunIds.includes(openId))
            detailReloadRef.current();

lib/console/useRunHistory.ts:141

  • reload creates a separate fetch controller but never stores it in appendController, while the effect cleanup only aborts the initial controller and that ref. If billing sync or Retry triggers a reload and the component unmounts or changes account/query, the reload continues consuming network/server work; repeated reloads can also overlap. Store this controller in the shared ref so the hook cleanup and subsequent reloads can cancel it.
    lib/runs/billing.ts:55
  • This summary sums every priced manifest, including a manifest that is still only prepared. Since recordManifestUsage currently stamps matching rows without checking accepted, a stale or inconsistent aggregate for an unaccepted payment can be presented as a charge even though no accepted payment attempt exists. Restrict priced to accepted manifests (while keeping the existing pending check for accepted-but-unpriced manifests) so only accepted attempts contribute to the run cost.
    lib/runs/store.ts:198
  • When a durable output reuses a legacy mcp_assets row whose runId is null, the output edge is still recorded in runAssetLinks, but this condition forces the asset to input because asset.runId !== row.id. The drawer then filters it out of outputAssets, so the persisted output is not previewable and its lineage is mislabeled; use the link direction when an edge exists and fall back to runId only for unlinked assets.
  • Files reviewed: 104/109 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 11, 2026 19:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

One critical and nine moderate unresolved findings remain, including media sanitization, preview selection, fixture, refresh, token-cache, and origin issues.

Get a fresh assessment by requesting another Copilot review.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (9)

app/api/console/runs/[id]/route.ts:15

  • The list route seeds preview fixtures, but this detail route does not. On a direct preview deep link, the detail request can arrive before (or without) the list request and getOwnRun then returns run_not_found for a fixture that should exist, leaving the drawer unavailable. Seed fixtures in this route as well, guarded by previewFixturesEnabled(), before loading the requested run.
    return Response.json(publicRunDetail(result), { headers: RUN_HEADERS });

components/console/CallDetailDrawer.tsx:1145

  • The fallback always chooses the first renderable output by media type, even when that asset is already unavailable or its exact expiry has passed. If a run has an expired preview followed by a valid preview, the drawer selects the expired one and shows “Media unavailable” instead of the usable asset. Exclude unavailable/expired assets from this fallback while retaining the final fallback for runs with no playable output.
    ) ?? outputAssets?.find((item) => /^(?:image|video|audio)(?:\/|$)/i.test(item.mediaType ?? "")) ?? outputAssets?.[0];
  let media = row
    ? mediaSpecForRow({

components/console/CallDetailDrawer.tsx:1143

  • Choosing the first playable output does not reliably select a 3D preview. detail() orders assets by createdAt, id, while output links carry the meaningful ordinal and inserts in one transaction can share a timestamp with random IDs; a texture can therefore precede preview_image and become the drawer's default. Preserve/use the output-link order or an explicit preview designation before applying the generic playable fallback.
    ) ?? outputAssets?.find((item) => /^(?:image|video|audio)(?:\/|$)/i.test(item.mediaType ?? "")) ?? outputAssets?.[0];

components/console/CallDetailDrawer.tsx:1404

  • This renders every asset, including role: "input", as a selectable button, but asset is resolved only from outputAssets above. Clicking an Input therefore changes selectedAsset without changing the preview or aria-pressed state, making this control misleading; either make input rows non-interactive or include input assets in the supported preview selection.
                                    <button
                                      type="button"
                                      aria-pressed={asset?.id === item.id}
                                      onClick={() =>
                                        setSelectedAsset({

components/console/CallsSection.tsx:44

  • When a deep-linked requestId is not on the first 50-row page, this array contains 51 IDs. The billing-sync route rejects any body over 50 (app/api/console/runs/billing-sync/route.ts:15), so the refresh fails for the entire page rather than updating any visible run. Keep the request within the server limit or split the refresh into bounded batches.
      ? [requestId, ...ids.filter((id) => id !== requestId)]
      : ids;
  }, [history.page, requestId]);

components/console/CallsSection.tsx:92

  • This component accepts requestId as either a durable run ID or a gatewayRequestId below, but it passes the raw value to both useRunDetail and billing sync. A deep link such as ?request=job_* therefore calls an API that only looks up runs.id, and the billing refresh receives an ID that refreshOwnedRunBilling also cannot match; the row may be recognized locally while its detail and cost never load. Resolve gateway IDs to the owner-scoped durable run ID before these requests, or make the server endpoints resolve both forms.
    detail.detail &&
    (detail.detail.id === requestId ||
      detail.detail.gatewayRequestId === requestId)

lib/console/end-user-token-cache.ts:65

  • now is captured before the asynchronous mint resolves, so a slow token response starts its cache lifetime too early. For a short-lived token this can make the freshly returned token fail the 30-second refresh-skew check immediately and can cause unnecessary remints; anchor expiresAt to completion (while preserving the injectable clock used by tests).
    lib/console/end-user-token-cache.ts:71
  • The failed-mint fallback compares the cached expiry with the now captured when the mint started. If the refresh request stalls until that token has actually expired, this branch still returns the expired token and the caller sends an invalid credential; compare against the current clock (or elapsed time from the injected clock) before falling back.
    lib/runs/preview-fixtures.ts:62
  • These fixture URLs are built from the request host, but assetReferences/ownedAssetReference and every generated signed URL only accept publicOrigin(). On a normal Vercel preview alias that differs from the configured application origin, the variation's first-party input is not linked (and is later stripped as foreign), while returned links point at the other origin. Use one canonical origin for all fixture URLs and public asset URLs.
  • Files reviewed: 104/109 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread lib/runs/outputs.ts
Comment on lines 65 to 66
}
const row = value as Record<string, unknown>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants