Skip to content

refactor: reconcile hexagonal architecture, segregate ports, extract usecases and remove db leaks - #61

Merged
TechHypeXP merged 4 commits into
mainfrom
refactor/hexagonal-reconciliation
Jun 10, 2026
Merged

refactor: reconcile hexagonal architecture, segregate ports, extract usecases and remove db leaks#61
TechHypeXP merged 4 commits into
mainfrom
refactor/hexagonal-reconciliation

Conversation

@TechHypeXP

@TechHypeXP TechHypeXP commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

This PR contains the complete core platform stabilization and hexagonal architecture alignment matching Option B in the implementation plan (Waves 1-3 + remaining refactoring tasks for search, billing, and chat routes).


Summary by cubic

Refactored the app to a clean hexagonal architecture by splitting ports, hiding database access behind adapters, and adding a CreateAnalysisUseCase to run analysis creation end-to-end. This removes direct @supabase/supabase-js calls from API routes, tightens billing/search/chat flows, adds Sentry instrumentation, and fixes SSE abort leaks.

  • Refactors

    • Segregated ports for auth, traffic, billing, persistence, chat, model resolution, ingestion, and crypto; updated adapters to implement them.
    • Rewrote /api/analyses, chat (conversations/messages), search, and billing webhook routes to use adapters/use case only.
    • Aligned the worker by renaming ports to ...Port and updating services; instrumented Sentry via @sentry/hono and added auth-failure logging in web/middleware.ts.
    • Added AGPL-3.0 + custom addendum, SECURITY/CONTRIBUTING docs, audit and implementation plan; bumped @hex-yt-intel/web and youtube-intelligence-worker to 1.5.2 and upgraded postcss.
    • Introduced CreateAnalysisUseCase to handle traffic/billing gates, ingestion, model resolution, token minting, and persistence.
  • Bug Fixes

    • Fixed SSE cleanup with an AbortController to stop duplicate streams and leaks.
    • Enabled Paddle webhook signature verification with a safe dev fallback; persisted events via SupabasePersistenceAdapter.
    • Standardized chat token signing through StreamTokenAdapter.signChatToken and enforced message persistence via the chat persistence port.

Written for commit fc0d270. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

Release Notes

  • New Features

    • Chat conversations now include analysis grounding and contextual information
    • Idempotent message creation prevents accidental duplicates
  • Bug Fixes

    • Improved stream request abort handling for better resource cleanup
    • Enhanced webhook signature verification for billing security
  • Documentation

    • Added contribution guidelines, security policy, and licensing terms
    • Added implementation roadmap and audit documentation

@sourcery-ai sourcery-ai Bot 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.

Sorry @TechHypeXP, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
hex-yt-intel Ready Ready Preview, Comment Jun 10, 2026 9:11am

@supabase

supabase Bot commented Jun 10, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project adnmbikaqnxivalqoild because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@TechHypeXP, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 23 minutes and 34 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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 084ca542-0dc4-4d45-9e46-d1997134ea03

📥 Commits

Reviewing files that changed from the base of the PR and between 278b8fe and fc0d270.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • README.md
  • web/middleware.ts
  • web/package.json
  • worker/package.json
  • worker/src/worker.ts

Walkthrough

This PR executes a multi-wave architectural refactoring toward hexagonal/clean architecture patterns. The codebase transitions from monolithic ports (e.g., IQuotaPort, IIngestionPort) to focused single-responsibility interfaces, introduces domain use cases to encapsulate business logic, refactors API routes to use adapters and dependency injection, and improves streaming resilience with request cancellation. Changes span contract definitions, adapter implementations, domain orchestration, route handlers, and infrastructure improvements.

Changes

Hexagonal Architecture Refactoring

Layer / File(s) Summary
Legal and Policy Foundation
.github/CONTRIBUTING.md, .github/SECURITY.md, LICENSE, docs/legal/*
Establishes AGPL-3.0 licensing with custom addendum, contribution terms, security policy, and notice documentation.
Implementation Roadmap Documentation
docs/specs/IMPLEMENTATION_PLAN.md
Defines three-wave refactoring strategy: Wave 1 addresses security/latency quick wins; Wave 2 segregates ports into single-responsibility contracts; Wave 3 moves business logic into domain use cases and cleans route handlers.
Port Interface Segregation and Renaming
web/lib/ports/AuthPort.ts, web/lib/ports/BillingQuotaPort.ts, web/lib/ports/TrafficGuardPort.ts, web/lib/ports/QuotaPort.ts, web/lib/ports/MetadataIngestionPort.ts, web/lib/ports/ModelResolutionPort.ts, web/lib/ports/CryptographicTokenPort.ts, web/lib/ports/IngestionPort.ts, web/lib/ports/PersistencePort.ts, web/lib/ports/ChatPersistencePort.ts, web/lib/ports/index.ts
Renames I* interfaces to *Port convention; splits IQuotaPort into TrafficGuardPort (DDoS/rate limits) and BillingQuotaPort (billing quotas); splits IIngestionPort into MetadataIngestionPort, ModelResolutionPort, and CryptographicTokenPort; extends PersistencePort with analysis/user history/tier operations; adds ChatPersistencePort for conversation and message CRUD.
Worker Port Interfaces Refactoring
worker/src/ports/ReasoningEnginePort.ts, worker/src/ports/LLMCascadePort.ts, worker/src/ports/PromptBuilderPort.ts, worker/src/ports/PersistenceRepositoryPort.ts, worker/src/ports/TranscriptProviderPort.ts
Renames worker port interfaces from I* to *Port convention, aligning with web-side naming and improving clarity of port roles.
Web Adapter Implementations for New Ports
web/lib/adapters/PostgresBillingAdapter.ts, web/lib/adapters/RedisTrafficAdapter.ts, web/lib/adapters/SettingsModelAdapter.ts, web/lib/adapters/StreamTokenAdapter.ts, web/lib/adapters/SupabaseAuthAdapter.ts, web/lib/adapters/SupabasePersistenceAdapter.ts, web/lib/adapters/WorkerIngestionAdapter.ts
Updates adapters to implement new port contracts: BillingQuotaPort, TrafficGuardPort, ModelResolutionPort, CryptographicTokenPort, AuthPort; extends SupabasePersistenceAdapter with 13+ new methods for chat persistence (conversations, messages, idempotent creation), analysis history, user tier updates, and result persistence; removes obsolete methods from split adapters.
Worker Adapter and Service Updates for New Ports
worker/src/services/LLMCascade.ts, worker/src/services/PromptBuilder.ts, worker/src/services/ReasoningEngine.ts, worker/src/services/TranscriptExtractor.ts, worker/src/services/UpstashCacheAdapter.ts, worker/src/worker.ts
Wires worker services to implement renamed *Port contracts with updated dependency injection types; re-exports port types from ReasoningEngine for consumer modules.
Domain Use Case for Analysis Creation
web/lib/usecases/CreateAnalysisUseCase.ts
Encapsulates analysis creation workflow: validates URL, checks cache, enforces traffic/billing gates in parallel with refund logic on blocked traffic, fetches metadata/transcript with billing refunds on failure, validates transcript, determines persona, persists processing stub (with refunds on failure), resolves models, signs token, and returns discriminated UseCaseResult (cache_hit, processing, or error).
API Route Refactoring to Use Adapters and Use Cases
web/app/api/analyses/persist/route.ts, web/app/api/analyses/route.ts, web/app/api/billing/webhook/route.ts, web/app/api/chat/conversations/route.ts, web/app/api/chat/conversations/[id]/messages/route.ts, web/app/api/chat/persist/route.ts, web/app/api/search/route.ts
Refactors all routes to use adapters and use cases instead of direct Supabase: POST /api/analyses delegates to CreateAnalysisUseCase, GET /api/analyses uses adapter history, analyses persist uses adapter lookup/update, billing webhook uses adapter tier updates, chat routes implement adapter-driven auth/persistence with idempotent message creation via clientMsgId, search enriches results via adapter findAnalysisById.
Streaming Resilience: AbortController Integration
web/hooks/useSSEStream.ts
Adds AbortController lifecycle to manage prep and stream request cancellation, suppresses error/status updates on abort, and conditions cleanup setIsLoading(false) on non-abort completion.
Type Updates and Package Versioning
web/lib/types/chat.ts, web/lib/ports/index.ts, package.json, web/package.json, worker/package.json
Adds userId field to ChatConversation; updates port barrel exports to renamed and new interfaces; bumps all package versions to 1.5.2.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


Possibly related PRs

  • Hex-Tech-Lab/hex-yt-intel#30: Introduces idempotent message creation via clientMsgId in chat message routes, directly complementing this PR's adapter-driven chat persistence wiring.
  • Hex-Tech-Lab/hex-yt-intel#37: Refactors worker ReasoningEngine and related services to hexagonal ports (same *Port naming pattern and DI structure applied in this PR).
  • Hex-Tech-Lab/hex-yt-intel#57: Implements UCIS v2.0 structured payload persistence and HMAC verification, aligning with this PR's persistence adapter refactoring in /api/analyses/persist.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly summarizes the main architectural refactoring work: hexagonal architecture reconciliation, port segregation, usecase extraction, and database leak removal.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/hexagonal-reconciliation
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch refactor/hexagonal-reconciliation

@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@cubic-dev-ai cubic-dev-ai Bot 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.

11 issues found across 51 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="web/app/api/chat/persist/route.ts">

<violation number="1" location="web/app/api/chat/persist/route.ts:37">
P1: Raw adapter exceptions can now leak internal error details in API responses. Return a generic 500 message for persistence failures instead of propagating `error.message` to clients.</violation>
</file>

<file name=".github/SECURITY.md">

<violation number="1" location=".github/SECURITY.md:4">
P3: Email wrapped in square brackets renders as literal `[privacy@hex-yt-intel.com]` in standard markdown. Use `<privacy@hex-yt-intel.com>` or a proper `[email](mailto:...)` link for correct rendering.</violation>
</file>

<file name="web/lib/ports/BillingQuotaPort.ts">

<violation number="1" location="web/lib/ports/BillingQuotaPort.ts:15">
P3: Endpoint union is duplicated inline instead of shared type. This increases risk of type drift across ports/adapters when endpoints change.</violation>
</file>

<file name="web/lib/ports/QuotaPort.ts">

<violation number="1" location="web/lib/ports/QuotaPort.ts:1">
P2: Framework leak: `NextResponse` from `next/server` imported in a port interface. Hexagonal architecture requires ports to be framework-agnostic. The adapter layer should construct framework responses; the port should define domain-level result types (status code, headers, body).</violation>
</file>

<file name="web/lib/ports/ChatPersistencePort.ts">

<violation number="1" location="web/lib/ports/ChatPersistencePort.ts:26">
P1: Conversation/message lookups are keyed by `conversationId` only, so ownership checks are not enforced by contract. This can permit cross-user data access if an adapter queries by ID without extra guards.</violation>
</file>

<file name="web/hooks/useSSEStream.ts">

<violation number="1" location="web/hooks/useSSEStream.ts:227">
P1: Aborted runs can leave global `isLoading` stuck true because final cleanup is gated on `!currentSignal.aborted`. This can keep UI actions disabled after navigation/unmount aborts.</violation>
</file>

<file name="web/lib/ports/CryptographicTokenPort.ts">

<violation number="1" location="web/lib/ports/CryptographicTokenPort.ts:1">
P2: Cross-port type coupling reintroduces boundary leakage. Move StreamToken to a neutral shared contract file and import it from both ports.</violation>
</file>

<file name="LICENSE">

<violation number="1" location="LICENSE:25">
P2: Typo in AGPLv3 preamble: "do you know" should be "you know" — the word "do" is erroneously inserted, breaking the standard preamble text.</violation>

<violation number="2" location="LICENSE:47">
P3: LICENSE file is incomplete — after stating the terms "follow", it only provides a URL placeholder. Standard practice is to include the full AGPLv3 license text verbatim so the legal terms are self-contained in the repository.</violation>
</file>

<file name="web/app/api/billing/webhook/route.ts">

<violation number="1" location="web/app/api/billing/webhook/route.ts:25">
P0: Missing await on Paddle webhook unmarshal leaves event as a Promise and skips all subscription handling.</violation>
</file>

<file name="web/lib/ports/PersistencePort.ts">

<violation number="1" location="web/lib/ports/PersistencePort.ts:134">
P3: `status` was added to the port contract but is not consumed by the adapter. This is dead API surface and can drift from actual persisted state semantics.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

if (!secret) {
throw new Error('PADDLE_WEBHOOK_SECRET is required');
}
event = paddle.webhooks.unmarshal(body, secret, signature);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0: Missing await on Paddle webhook unmarshal leaves event as a Promise and skips all subscription handling.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/api/billing/webhook/route.ts, line 25:

<comment>Missing await on Paddle webhook unmarshal leaves event as a Promise and skips all subscription handling.</comment>

<file context>
@@ -9,35 +10,40 @@ import { getSupabaseClientWithAuth } from '@/lib/supabase';
+      if (!secret) {
+        throw new Error('PADDLE_WEBHOOK_SECRET is required');
+      }
+      event = paddle.webhooks.unmarshal(body, secret, signature);
+    }
+
</file context>
Suggested change
event = paddle.webhooks.unmarshal(body, secret, signature);
event = await paddle.webhooks.unmarshal(body, secret, signature);

}
if (!conv || conv.user_id !== userId) {
console.warn('[chat/persist] Conversation/owner mismatch', { conversationId });
const conv = await persistenceAdapter.getConversation({ conversationId });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Raw adapter exceptions can now leak internal error details in API responses. Return a generic 500 message for persistence failures instead of propagating error.message to clients.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/api/chat/persist/route.ts, line 37:

<comment>Raw adapter exceptions can now leak internal error details in API responses. Return a generic 500 message for persistence failures instead of propagating `error.message` to clients.</comment>

<file context>
@@ -35,34 +31,21 @@ export async function POST(request: NextRequest) {
-    }
-    if (!conv || conv.user_id !== userId) {
-      console.warn('[chat/persist] Conversation/owner mismatch', { conversationId });
+    const conv = await persistenceAdapter.getConversation({ conversationId });
+    if (!conv || conv.userId !== userId) {
+      console.warn('[chat/persist] Conversation/owner mismatch or not found', { conversationId });
</file context>

@@ -0,0 +1,82 @@
import type { ChatConversation, ChatMessage } from '@/lib/types/chat';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Conversation/message lookups are keyed by conversationId only, so ownership checks are not enforced by contract. This can permit cross-user data access if an adapter queries by ID without extra guards.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/lib/ports/ChatPersistencePort.ts, line 26:

<comment>Conversation/message lookups are keyed by `conversationId` only, so ownership checks are not enforced by contract. This can permit cross-user data access if an adapter queries by ID without extra guards.</comment>

<file context>
@@ -0,0 +1,82 @@
+   * Retrieve a single conversation.
+   */
+  getConversation(params: {
+    conversationId: string;
+  }): Promise<ChatConversation | null>;
+
</file context>

Comment thread web/hooks/useSSEStream.ts
Comment on lines +227 to +229
if (!currentSignal.aborted) {
setIsLoading(false);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Aborted runs can leave global isLoading stuck true because final cleanup is gated on !currentSignal.aborted. This can keep UI actions disabled after navigation/unmount aborts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/hooks/useSSEStream.ts, line 227:

<comment>Aborted runs can leave global `isLoading` stuck true because final cleanup is gated on `!currentSignal.aborted`. This can keep UI actions disabled after navigation/unmount aborts.</comment>

<file context>
@@ -200,7 +224,9 @@ export function useSSEStream() {
           // action button is never left disabled after a partial/interrupted stream,
           // regardless of which exit path ran.
-          setIsLoading(false);
+          if (!currentSignal.aborted) {
+            setIsLoading(false);
+          }
</file context>
Suggested change
if (!currentSignal.aborted) {
setIsLoading(false);
}
if (abortControllerRef.current?.signal === currentSignal) {
setIsLoading(false);
}

@@ -0,0 +1,11 @@
import type { NextResponse } from 'next/server';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Framework leak: NextResponse from next/server imported in a port interface. Hexagonal architecture requires ports to be framework-agnostic. The adapter layer should construct framework responses; the port should define domain-level result types (status code, headers, body).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/lib/ports/QuotaPort.ts, line 1:

<comment>Framework leak: `NextResponse` from `next/server` imported in a port interface. Hexagonal architecture requires ports to be framework-agnostic. The adapter layer should construct framework responses; the port should define domain-level result types (status code, headers, body).</comment>

<file context>
@@ -0,0 +1,11 @@
+import type { NextResponse } from 'next/server';
+
+/** Result of a quota gate check. */
</file context>

Comment thread LICENSE
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you do you know you can do these things.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Typo in AGPLv3 preamble: "do you know" should be "you know" — the word "do" is erroneously inserted, breaking the standard preamble text.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At LICENSE, line 25:

<comment>Typo in AGPLv3 preamble: "do you know" should be "you know" — the word "do" is erroneously inserted, breaking the standard preamble text.</comment>

<file context>
@@ -0,0 +1,47 @@
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you do you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
</file context>

Comment thread .github/SECURITY.md
# Security Policy

## Reporting Vulnerabilities
Please report security vulnerabilities to [privacy@hex-yt-intel.com].

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Email wrapped in square brackets renders as literal [privacy@hex-yt-intel.com] in standard markdown. Use <privacy@hex-yt-intel.com> or a proper [email](mailto:...) link for correct rendering.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/SECURITY.md, line 4:

<comment>Email wrapped in square brackets renders as literal `[privacy@hex-yt-intel.com]` in standard markdown. Use `<privacy@hex-yt-intel.com>` or a proper `[email](mailto:...)` link for correct rendering.</comment>

<file context>
@@ -0,0 +1,9 @@
+# Security Policy
+
+## Reporting Vulnerabilities
+Please report security vulnerabilities to [privacy@hex-yt-intel.com]. 
+
+## Policy on Automated Abuse
</file context>
Suggested change
Please report security vulnerabilities to [privacy@hex-yt-intel.com].
Please report security vulnerabilities to <privacy@hex-yt-intel.com>.

userId: string;
tier: UserTier;
email?: string;
endpoint: 'analyses' | 'search' | 'checkout';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Endpoint union is duplicated inline instead of shared type. This increases risk of type drift across ports/adapters when endpoints change.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/lib/ports/BillingQuotaPort.ts, line 15:

<comment>Endpoint union is duplicated inline instead of shared type. This increases risk of type drift across ports/adapters when endpoints change.</comment>

<file context>
@@ -0,0 +1,22 @@
+    userId: string;
+    tier: UserTier;
+    email?: string;
+    endpoint: 'analyses' | 'search' | 'checkout';
+  }): Promise<QuotaGateResult>;
+
</file context>

Comment thread LICENSE

TERMS AND CONDITIONS

[Full AGPLv3 text: https://www.gnu.org/licenses/agpl-3.0.txt]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: LICENSE file is incomplete — after stating the terms "follow", it only provides a URL placeholder. Standard practice is to include the full AGPLv3 license text verbatim so the legal terms are self-contained in the repository.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At LICENSE, line 47:

<comment>LICENSE file is incomplete — after stating the terms "follow", it only provides a URL placeholder. Standard practice is to include the full AGPLv3 license text verbatim so the legal terms are self-contained in the repository.</comment>

<file context>
@@ -0,0 +1,47 @@
+
+                        TERMS AND CONDITIONS
+
+[Full AGPLv3 text: https://www.gnu.org/licenses/agpl-3.0.txt]
</file context>

payload: any;
model: string | null;
validationPassed: boolean;
status: 'done' | 'interrupted';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: status was added to the port contract but is not consumed by the adapter. This is dead API surface and can drift from actual persisted state semantics.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/lib/ports/PersistencePort.ts, line 134:

<comment>`status` was added to the port contract but is not consumed by the adapter. This is dead API surface and can drift from actual persisted state semantics.</comment>

<file context>
@@ -74,4 +74,64 @@ export interface IPersistencePort {
+    payload: any;
+    model: string | null;
+    validationPassed: boolean;
+    status: 'done' | 'interrupted';
+    validationReport: any;
+  }): Promise<void>;
</file context>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (13)
web/lib/adapters/SettingsModelAdapter.ts (1)

14-22: ⚠️ Potential issue | 🟡 Minor

Fix misleading tier documentation in SettingsModelAdapter (and wire tier cascade when not in trial mode)

web/lib/adapters/SettingsModelAdapter.ts ignores the _tier parameter (returns Haiku-only in COMMERCIAL_TRIAL_MODE), yet its JSDoc claims “WorkerIngestionAdapter handles tier logic”—but WorkerIngestionAdapter only fetches metadata/transcript and does not resolve models. Tier-based cascade logic exists in web/lib/services/settings.ts (resolveModelCascade(tier, kind)) but isn’t used by SettingsModelAdapter.

🤖 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 `@web/lib/adapters/SettingsModelAdapter.ts` around lines 14 - 22,
SettingsModelAdapter.resolveModels currently ignores the _tier argument and
always returns Haiku when COMMERCIAL_TRIAL_MODE or kind === 'chat'; update it to
use the existing tier cascade logic by calling resolveModelCascade(_tier, kind)
from web/lib/services/settings.ts when not in COMMERCIAL_TRIAL_MODE so model
lists honor tier-based fallbacks, while preserving the special
COMMERCIAL_TRIAL_MODE behavior; also update the JSDoc on SettingsModelAdapter to
stop claiming "WorkerIngestionAdapter handles tier logic" and state that
SettingsModelAdapter resolves models via resolveModelCascade unless in trial
mode.
package.json (1)

19-21: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Pin Node.js version to 24.16.0 LTS for consistency.

The coding guidelines require pinning Node.js to version 24.16.0 LTS for CI/deployment. The current engines field specifies >=24.0.0, which allows any 24.x version. While .nvmrc or .node-version files may pin the version for local development, the package.json should also reflect this requirement for clarity.

📝 Proposed fix
   "engines": {
-    "node": ">=24.0.0",
+    "node": "24.16.0",
     "pnpm": ">=9.0.0"
   }

As per coding guidelines: "{.nvmrc,.node-version,*.json}: Pin Node.js to version 24.16.0 LTS for CI/deployment"

🤖 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 `@package.json` around lines 19 - 21, Update the engines entry in package.json
to pin Node.js to 24.16.0 LTS instead of a range: replace the "node": ">=24.0.0"
value with "24.16.0" (leave "pnpm" as-is), ensuring the engines field explicitly
requires Node 24.16.0 for CI/deployment consistency.

Source: Coding guidelines

worker/src/services/PromptBuilder.ts (1)

4-4: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update stale interface reference in documentation comment.

The comment references IPromptBuilder, but the code now implements PromptBuilderPort (line 13).

📝 Proposed fix
-/**
- * PromptBuilder - Pure Service (stateless)
- *
- * Implements IPromptBuilder. Wraps getUCISPrompt — the prompt IP is constructed
- * here and bundled into the worker by esbuild, so it never leaves the server.
- * Config-only / stateless: safe to share across requests.
- */
+/**
+ * PromptBuilder - Pure Service (stateless)
+ *
+ * Implements PromptBuilderPort. Wraps getUCISPrompt — the prompt IP is constructed
+ * here and bundled into the worker by esbuild, so it never leaves the server.
+ * Config-only / stateless: safe to share across requests.
+ */
🤖 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 `@worker/src/services/PromptBuilder.ts` at line 4, The top-of-file doc comment
incorrectly references the old interface name IPromptBuilder; update the comment
to reference the current interface PromptBuilderPort and any related identifiers
(e.g., the class PromptBuilder and the getUCISPrompt method) so the
documentation matches the implementation; ensure wording reflects that
PromptBuilder implements PromptBuilderPort rather than IPromptBuilder.
worker/src/services/LLMCascade.ts (1)

4-4: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update stale interface reference in documentation comment.

The comment references ILLMCascade, but the code now implements LLMCascadePort (line 31).

📝 Proposed fix
-/**
- * LLMCascade - LLM Transport Adapter (config-only)
- *
- * Implements ILLMCascade. Owns the OpenRouter multi-model fallback chain and the
- * two transport adapters (streaming + non-streaming). Config-only (apiKey): all
- * request-scoped state stays in method locals, so it is race-free when shared.
- */
+/**
+ * LLMCascade - LLM Transport Adapter (config-only)
+ *
+ * Implements LLMCascadePort. Owns the OpenRouter multi-model fallback chain and the
+ * two transport adapters (streaming + non-streaming). Config-only (apiKey): all
+ * request-scoped state stays in method locals, so it is race-free when shared.
+ */
🤖 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 `@worker/src/services/LLMCascade.ts` at line 4, Update the stale doc comment
that mentions ILLMCascade to reference the current interface LLMCascadePort:
find the top-of-file comment in LLMCascade.ts that starts "Implements
ILLMCascade" and change the interface name to "LLMCascadePort" so the
documentation matches the implemented symbol (LLMCascade class and its
implemented interface LLMCascadePort).
worker/src/services/TranscriptExtractor.ts (1)

5-5: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update stale interface reference in documentation comment.

The comment references ITranscriptProvider, but the code now implements TranscriptProviderPort (line 26).

📝 Proposed fix
 /**
  * TranscriptExtractor - Pure Service
  *
  * HEXAGONAL ARCHITECTURE:
- * - PORT: ITranscriptProvider (fetch(videoId: string): Promise<string>)
+ * - PORT: TranscriptProviderPort (fetch(videoId: string): Promise<TranscriptResult>)
  * - ADAPTER: YouTube Caption API + Decodo fallback
  * - DOMAIN: Caption parsing, language fallback, timeout protection
  *
  * Extracts video transcripts from YouTube's timedtext API with:
  * - English language prioritization
  * - Fallback to first available language
  * - 5-second timeout per request
  * - XML/JSON parsing and reconstruction
  */
🤖 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 `@worker/src/services/TranscriptExtractor.ts` at line 5, Update the stale
documentation comment that references ITranscriptProvider to the current port
name TranscriptProviderPort; locate the docblock above the TranscriptExtractor
class (or where the comment appears) and replace the old interface reference
with TranscriptProviderPort and, if desired, confirm the documented
method/signature matches the actual port method (fetch(videoId: string):
Promise<string>).
worker/src/services/UpstashCacheAdapter.ts (1)

4-4: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update stale interface reference in documentation comment.

The comment references IPersistenceRepository, but the code now implements PersistenceRepositoryPort (line 13).

📝 Proposed fix
 /**
  * UpstashCacheAdapter - Persistence Adapter (config-only)
  *
- * Implements IPersistenceRepository. The SOLE place Upstash REST `fetch` calls
+ * Implements PersistenceRepositoryPort. The SOLE place Upstash REST `fetch` calls
  * live — core reasoning never touches the Upstash client directly, only this port.
  * Config-only (url + token): no request-scoped mutable state, safe to share.
  */
🤖 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 `@worker/src/services/UpstashCacheAdapter.ts` at line 4, The doc comment at the
top of UpstashCacheAdapter.ts incorrectly references IPersistenceRepository;
update that comment to reference the current interface name
PersistenceRepositoryPort (and optionally mention the implementing class
UpstashCacheAdapter) so the documentation matches the actual implementation
(class UpstashCacheAdapter implements PersistenceRepositoryPort).
worker/src/services/ReasoningEngine.ts (1)

5-10: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update stale interface references in documentation comment.

The comment block references the old I* interface names (IReasoningEngine, IPromptBuilder, ILLMCascade, IPersistenceRepository), but the code now uses the *Port naming convention (lines 23-26, 42-47).

📝 Proposed fix
 /**
  * ReasoningEngine - Thin Orchestrator (Hexagonal-Lite)
  *
  * HEXAGONAL ARCHITECTURE:
- * - PORT: IReasoningEngine (executeAndStream(context, handlers) / execute(context))
+ * - PORT: ReasoningEnginePort (executeAndStream(context, handlers) / execute(context))
  * - DEPENDENCIES (constructor DI):
- *     IPromptBuilder         — UCIS v5.1 prompt synthesis (IP stays server-side)
- *     ILLMCascade            — OpenRouter multi-model fallback (streaming + batch)
+ *     PromptBuilderPort      — UCIS v5.1 prompt synthesis (IP stays server-side)
+ *     LLMCascadePort         — OpenRouter multi-model fallback (streaming + batch)
  *     ValidationService      — 11D structure validation
- *     IPersistenceRepository — Upstash KV cache (optional; insulated behind port)
+ *     PersistenceRepositoryPort — Upstash KV cache (optional; insulated behind port)
  *
  * The engine owns ONLY orchestration + the per-stream BracketBuffer parser. It holds no
  * request-scoped mutable state on shared sub-services — each is stateless/config-only,
  * and a fresh BracketBuffer is created per stream — so DI is race-free even though
  * worker.ts constructs the engine per-request.
  *
  * BOUNDARY: Accepts domain objects (transcript, metadata), emits domain events
  * (delta text, dimension fragments). Never touches raw HTTP Request/Response or SSE.
  * The orchestrator (worker.ts) owns transport; this engine owns reasoning.
  */
🤖 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 `@worker/src/services/ReasoningEngine.ts` around lines 5 - 10, Update the stale
interface names in the top comment of ReasoningEngine.ts: replace
IReasoningEngine with ReasoningEnginePort, IPromptBuilder with
PromptBuilderPort, ILLMCascade with LLMCascadePort, and IPersistenceRepository
with PersistenceRepositoryPort; also ensure the listed port methods
(executeAndStream, execute) and the constructor DI list match the actual symbols
used in the file (the class constructor and methods executeAndStream/execute) so
the docblock reflects the current *Port naming and method signatures.
worker/src/ports/PromptBuilderPort.ts (1)

2-2: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the comment header to match the renamed interface.

The comment header still references IPromptBuilder, but the interface has been renamed to PromptBuilderPort (Line 10).

📝 Proposed fix
 /**
- * IPromptBuilder — Domain Port (Hexagonal-Lite)
+ * PromptBuilderPort — Domain Port (Hexagonal-Lite)
  *
🤖 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 `@worker/src/ports/PromptBuilderPort.ts` at line 2, The comment header at the
top still references the old interface name IPromptBuilder but the interface has
been renamed to PromptBuilderPort; update the header text to reference
PromptBuilderPort (or a neutral description) so it matches the interface name
PromptBuilderPort in this file and avoid stale docs.
worker/src/ports/LLMCascadePort.ts (1)

2-2: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the comment header to match the renamed interface.

The comment header still references ILLMCascade, but the interface has been renamed to LLMCascadePort (Line 11).

📝 Proposed fix
 /**
- * ILLMCascade — Domain Port (Hexagonal-Lite)
+ * LLMCascadePort — Domain Port (Hexagonal-Lite)
  *
🤖 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 `@worker/src/ports/LLMCascadePort.ts` at line 2, The file header comment still
references the old interface name ILLMCascade; update that top comment text to
match the renamed interface LLMCascadePort so the header accurately reflects the
code (search for the header/comment block and replace occurrences of ILLMCascade
with LLMCascadePort, and verify any related comment lines reference
LLMCascadePort consistently with the interface declaration).
worker/src/ports/ReasoningEnginePort.ts (1)

2-2: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the comment header to match the renamed interface.

The comment header still references IReasoningEngine, but the interface has been renamed to ReasoningEnginePort (Line 91).

📝 Proposed fix
 /**
- * IReasoningEngine — Domain Port (Hexagonal-Lite)
+ * ReasoningEnginePort — Domain Port (Hexagonal-Lite)
  *
🤖 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 `@worker/src/ports/ReasoningEnginePort.ts` at line 2, The file header/comment
currently references the old interface name IReasoningEngine; update that
comment to reference the new interface name ReasoningEnginePort so the header
matches the renamed interface (look for the top-of-file comment and the
interface declaration named ReasoningEnginePort) — simply replace
IReasoningEngine with ReasoningEnginePort in the header text to keep
documentation consistent with the code.
worker/src/ports/PersistenceRepositoryPort.ts (1)

2-2: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update stale JSDoc reference.

The comment still references the old interface name IPersistenceRepository, but the interface was renamed to PersistenceRepositoryPort on Line 8.

📝 Proposed fix
-/**
- * IPersistenceRepository — Domain Port (Hexagonal-Lite)
+ * PersistenceRepositoryPort — Domain Port (Hexagonal-Lite)
🤖 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 `@worker/src/ports/PersistenceRepositoryPort.ts` at line 2, The JSDoc header
still refers to the old interface name "IPersistenceRepository"; update the
comment to reference the current interface name "PersistenceRepositoryPort" so
docs match the code — edit the top-of-file JSDoc (the comment above the
PersistenceRepositoryPort interface) to replace "IPersistenceRepository" with
"PersistenceRepositoryPort" and ensure any surrounding wording stays accurate.
worker/src/ports/TranscriptProviderPort.ts (1)

2-2: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update stale JSDoc reference.

The comment still references the old interface name ITranscriptProvider, but the interface was renamed to TranscriptProviderPort on Line 12.

📝 Proposed fix
-/**
- * ITranscriptProvider — Domain Port (Hexagonal-Lite)
+ * TranscriptProviderPort — Domain Port (Hexagonal-Lite)
🤖 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 `@worker/src/ports/TranscriptProviderPort.ts` at line 2, The file's JSDoc still
references the old interface name ITranscriptProvider; update that comment to
use the current interface name TranscriptProviderPort so the documentation
matches the code (search for the JSDoc block at the top that mentions
ITranscriptProvider and replace the name with TranscriptProviderPort, ensuring
any descriptions remain accurate).
web/lib/ports/PersistencePort.ts (1)

37-76: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Move interface documentation to /docs/ directory.

The coding guidelines require documentation to be kept in /docs/, not in code comments. The JSDoc comments throughout this file should be extracted to architecture documentation.

🤖 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 `@web/lib/ports/PersistencePort.ts` around lines 37 - 76, The file currently
contains JSDoc for the PersistencePort interface and its methods
(PersistencePort, findCachedAnalysis, upsertProcessingStub, persistAnalysis);
extract those block comments into a new architecture doc under /docs/ (e.g.,
docs/persistence-port.md) preserving the descriptions, parameter semantics,
return contracts, and the 8-dimension validation detail, then remove the
multi-line JSDoc blocks from web/lib/ports/PersistencePort.ts so the file only
exposes the TypeScript interface and method signatures (you may keep a
single-line comment if needed), and ensure the new docs reference the exact
symbol names (PersistencePort, findCachedAnalysis, upsertProcessingStub,
persistAnalysis) so developers can find the implementation.

Source: Coding guidelines

🤖 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 `@docs/audit/10X_REAUDIT_IV_COMBINED_2026_06_10.md`:
- Around line 19-26: Update the audit snapshot table entries for "Root Version",
"Web Version", and "Worker Version" from 1.4.1 / 1.4.6 / 1.5.1 to 1.5.2, and
change the "Version Drift" / H6 status to reflect no drift (or explicitly note
the doc is a pre-PR snapshot) so the doc's metrics match the current monorepo
state; edit the markdown table row labels "Root Version", "Web Version", "Worker
Version", "Version Drift" and the H6 status text in the same document to perform
this correction.

In `@docs/specs/IMPLEMENTATION_PLAN.md`:
- Around line 67-137: The document uses absolute file URIs (strings starting
with "file:///home/kellyb_dev/projects/hex-yt-intel/") which breaks portability;
replace every absolute file:///... reference with a relative repo path (e.g.,
change "file:///home/.../web/app/api/billing/webhook/route.ts" to
"web/app/api/billing/webhook/route.ts") throughout IMPLEMENTATION_PLAN.md,
including all entries under Step 1.1, Step 1.2, Step 1.3, Step 2.1, Step 2.2 and
Wave 3 targets; search for the "file:///" prefix and update referenced paths to
their relative equivalents so links and targets like
web/app/api/analyses/route.ts, web/hooks/useSSEStream.ts,
web/lib/ports/IIngestionPort.ts, web/lib/adapters/WorkerIngestionAdapter.ts,
etc., no longer use absolute file URIs.

In `@web/app/api/analyses/persist/route.ts`:
- Around line 67-75: The code loses type safety by casting row.validationReport
to any and mixes snake_case vs camelCase in the JSON blob; define a
ValidationReport interface (including fields like status, transcript_available,
model_used, valid, etc.), replace the any cast so priorReport is typed as
ValidationReport, and update usages around priorReport/newReport (and the
created newReport object) to use the typed properties consistently (preserve
existing snake_case fields if the stored JSON uses them) so TypeScript enforces
correct property names and types when reading/writing validationReport.

In `@web/app/api/analyses/route.ts`:
- Line 66: Replace the unsafe "as any" cast for explicitPersona by ensuring
validation.data.persona is typed/validated as PersonaId: update
AnalysisCreateSchema to declare persona as PersonaId (or perform a runtime type
guard) and then assign explicitPersona = validation.data.persona (or cast to
PersonaId only after validation); reference the explicitPersona assignment and
the AnalysisCreateSchema/PersonaId types to locate and fix the code path where
validation.data.persona is used.

In `@web/app/api/billing/webhook/route.ts`:
- Around line 19-26: The current development-mode fallback parses the body
without verification when secret is missing, which is risky if NODE_ENV is
misconfigured; update the webhook handling in route.ts so that instead of
silently using JSON.parse when secret is falsey and NODE_ENV === 'development'
you either (a) require an explicit opt-in like a new env flag (e.g.,
DEV_ALLOW_UNVERIFIED_WEBHOOKS) and throw/log a clear warning if that flag is not
set, or (b) always throw and log an error unless PADDLE_WEBHOOK_SECRET is
present; specifically modify the block that references secret, NODE_ENV,
paddle.webhooks.unmarshal, signature and event to check for
PADDLE_WEBHOOK_SECRET and the opt-in flag, emit a prominent warning via your
logger when falling back, and avoid silently accepting unverified payloads.
- Around line 30-47: The webhook currently calls
persistenceAdapter.updateUserTier(...) but that method
(SupabasePersistenceAdapter.updateUserTier) returns Promise<void> and can
silently no-op if no rows match; update
SupabasePersistenceAdapter.updateUserTier to perform the update with RETURNING
or inspect the update response (e.g., rowCount/returned rows) and throw a
descriptive error when zero rows were affected, so the webhook route's switch
(subscription.created/updated/canceled) will surface failures instead of
returning processed:true for unknown userIds; ensure the thrown error includes
the userId and requested tier to aid debugging.

In `@web/app/api/chat/conversations/`[id]/messages/route.ts:
- Around line 63-66: The POST handler in route.ts fetches the conversation via
persistenceAdapter.getConversation but never verifies ownership; update the
handler to retrieve the authenticated user's id (e.g., from the request/session
or existing auth helper used in this file) and compare it to the conversation
owner field on conv (e.g., conv.userId or conv.ownerId). If they don't match,
return a 403 JSON response and do not allow inserting the message; keep the
existing 404 check for missing conv and place the ownership check immediately
after fetching conv in the POST route.
- Around line 31-33: The GET handler currently returns messages from
persistenceAdapter.getMessages({ conversationId: id }) without verifying
ownership; update the handler to fetch the conversation (e.g., via
persistenceAdapter.getConversation or getConversations) for the given id and
compare its userId to identity.userId, and if they differ return a
403/NextResponse.json error instead of returning messages; only call
persistenceAdapter.getMessages and return NextResponse.json({ messages }) after
the ownership check passes.

In `@web/lib/adapters/SettingsModelAdapter.ts`:
- Line 6: The COMMERCIAL_TRIAL_MODE constant in SettingsModelAdapter is
hardcoded; remove the private static readonly COMMERCIAL_TRIAL_MODE and instead
read the flag from configuration or a settings service (e.g., inject a Config or
SettingsService into SettingsModelAdapter or import an app config module),
defaulting appropriately if missing; update any references in
SettingsModelAdapter to use the injected/config value and add a unit-testable
fallback so behavior can be controlled via env/config in production and tests.

In `@web/lib/adapters/SupabasePersistenceAdapter.ts`:
- Around line 145-173: The getUserHistory method (and other new methods listed
such as findAnalysisById, updateUserTier, getConversations, createConversation,
getConversation, updateConversationTitle, getMessages, findMessageByClientMsgId,
createMessage, findAssistantMessageAfter, getAnalysisGrounding,
findAnalysisForPersist, updateAnalysisResult) currently only logs errors with
console.error; update each method to also capture exceptions with Sentry (e.g.,
Sentry.captureException(error)) when an error occurs, and include contextual
metadata (method name and relevant identifiers like userId, analysisId,
conversationId) in the capture to improve observability; keep the existing
console.error and thrown error behavior but add the Sentry.captureException call
before rethrowing so failures are reported to Sentry consistently across the
adapter.
- Around line 165-172: The map callbacks use `any` for DB rows; define explicit
Supabase row interfaces (e.g., AnalysisRow, ConversationRow, MessageRow) that
match your table schemas and replace `any` in the mapping callbacks (the
`analysis` parameter in the analyses mapping, and similar parameters in the
conversation and message mapping blocks referenced at 238-246 and 335-342) with
those types; update the return mapping code to use the typed fields (video_id,
created_at, validation_passed, validation_report, etc.) so the compiler enforces
schema correctness across SupabasePersistenceAdapter.ts.
- Line 464: Replace the unsafe cast of validation_report to any by adding a
proper typed shape and a narrow check: define an interface/type like
ValidationReport with an optional status field, change accesses to use a typed
guard (e.g., check typeof data.validation_report === 'object' &&
data.validation_report !== null) or a small helper isValidationReport(obj): obj
is ValidationReport, then read (data.validation_report as
ValidationReport)?.status || 'incomplete'; update the mapping in
SupabasePersistenceAdapter (where data.validation_report is used) to use this
type/guard instead of (data.validation_report as any).

In `@web/lib/ports/ChatPersistencePort.ts`:
- Around line 3-82: Remove the JSDoc block comments from the ChatPersistencePort
interface and its methods (leave the exported interface and all method
signatures intact) and move the explanatory text into the project’s
documentation directory as an architecture doc; create a markdown entry that
documents the ChatPersistencePort interface and each method
(ChatPersistencePort, getConversations, createConversation, getConversation,
updateConversationTitle, getMessages, findMessageByClientMsgId, createMessage,
findAssistantMessageAfter, getAnalysisGrounding) preserving the original
descriptions and request/response shapes so the code remains unchanged except
for removed comments.

In `@web/lib/ports/CryptographicTokenPort.ts`:
- Around line 3-24: Remove the inline JSDoc comments from the
CryptographicTokenPort interface and its methods signAnalysisToken and
signChatToken and place that documentation into the project architecture
documentation (create a new docs entry describing the interface purpose and the
two method contracts: HMAC-signed streaming tokens bound to their respective IDs
and models). Update the code in CryptographicTokenPort to keep only the type
signatures (interface and method params/return types) with no descriptive
comments, and ensure the new docs clearly mirror the removed comments so
consumers can find the same information outside the source file.

In `@web/lib/ports/MetadataIngestionPort.ts`:
- Around line 5-29: Remove the inline JSDoc blocks from the
MetadataIngestionPort interface and its members (the top-level comment and the
comments for fetch, detectPersona, and buildJobMetadata) and instead add an
entry in the project documentation describing the interface responsibilities and
contracts: list MetadataIngestionPort, its methods fetch(videoId: string):
Promise<IngestionResult> (including the error/quotas behavior and that
transcript may be empty), detectPersona(params: {title, channelTitle,
explicitPersona?: PersonaId}): PersonaId, and buildJobMetadata(metadata:
VideoMetadata): AnalysisJobMetadata; keep type references (IngestionResult,
PersonaId, VideoMetadata, AnalysisJobMetadata) in the doc so implementers
understand expectations and remove the corresponding JSDoc comments from the
source.

In `@web/lib/ports/PersistencePort.ts`:
- Around line 111-136: The findAnalysisForPersist and updateAnalysisResult
method signatures use `any` for `validationReport` and `payload`; update them to
use concrete types: replace `validationReport: any` with a proper type such as
`ValidationReportInput` (or `ValidationReportInput | unknown` if you need a safe
fallback) in both the findAnalysisForPersist return type and the
updateAnalysisResult parameter, and change `payload: any` in
updateAnalysisResult to `payload: UCISPayloadV2 | null` (UCISPayloadV2 is
already imported) so both methods use explicit, safer types instead of `any`.

In `@web/lib/usecases/CreateAnalysisUseCase.ts`:
- Around line 25-28: The UseCaseResult type uses `any` for the `data` field
which erases type safety; replace `any` with explicit interfaces for each
discriminated branch (e.g., define CacheHitData, ProcessingData and use those in
the `UseCaseResult` union) so callers of the UseCase (and route handlers) can
rely on concrete shapes; update the exported `UseCaseResult` union to reference
these new types for `data` (and add or tighten optional `headers` types if
needed) and update impacted call sites to conform to the new data shapes (look
for references to UseCaseResult, cache_hit, and processing handling).

---

Outside diff comments:
In `@package.json`:
- Around line 19-21: Update the engines entry in package.json to pin Node.js to
24.16.0 LTS instead of a range: replace the "node": ">=24.0.0" value with
"24.16.0" (leave "pnpm" as-is), ensuring the engines field explicitly requires
Node 24.16.0 for CI/deployment consistency.

In `@web/lib/adapters/SettingsModelAdapter.ts`:
- Around line 14-22: SettingsModelAdapter.resolveModels currently ignores the
_tier argument and always returns Haiku when COMMERCIAL_TRIAL_MODE or kind ===
'chat'; update it to use the existing tier cascade logic by calling
resolveModelCascade(_tier, kind) from web/lib/services/settings.ts when not in
COMMERCIAL_TRIAL_MODE so model lists honor tier-based fallbacks, while
preserving the special COMMERCIAL_TRIAL_MODE behavior; also update the JSDoc on
SettingsModelAdapter to stop claiming "WorkerIngestionAdapter handles tier
logic" and state that SettingsModelAdapter resolves models via
resolveModelCascade unless in trial mode.

In `@web/lib/ports/PersistencePort.ts`:
- Around line 37-76: The file currently contains JSDoc for the PersistencePort
interface and its methods (PersistencePort, findCachedAnalysis,
upsertProcessingStub, persistAnalysis); extract those block comments into a new
architecture doc under /docs/ (e.g., docs/persistence-port.md) preserving the
descriptions, parameter semantics, return contracts, and the 8-dimension
validation detail, then remove the multi-line JSDoc blocks from
web/lib/ports/PersistencePort.ts so the file only exposes the TypeScript
interface and method signatures (you may keep a single-line comment if needed),
and ensure the new docs reference the exact symbol names (PersistencePort,
findCachedAnalysis, upsertProcessingStub, persistAnalysis) so developers can
find the implementation.

In `@worker/src/ports/LLMCascadePort.ts`:
- Line 2: The file header comment still references the old interface name
ILLMCascade; update that top comment text to match the renamed interface
LLMCascadePort so the header accurately reflects the code (search for the
header/comment block and replace occurrences of ILLMCascade with LLMCascadePort,
and verify any related comment lines reference LLMCascadePort consistently with
the interface declaration).

In `@worker/src/ports/PersistenceRepositoryPort.ts`:
- Line 2: The JSDoc header still refers to the old interface name
"IPersistenceRepository"; update the comment to reference the current interface
name "PersistenceRepositoryPort" so docs match the code — edit the top-of-file
JSDoc (the comment above the PersistenceRepositoryPort interface) to replace
"IPersistenceRepository" with "PersistenceRepositoryPort" and ensure any
surrounding wording stays accurate.

In `@worker/src/ports/PromptBuilderPort.ts`:
- Line 2: The comment header at the top still references the old interface name
IPromptBuilder but the interface has been renamed to PromptBuilderPort; update
the header text to reference PromptBuilderPort (or a neutral description) so it
matches the interface name PromptBuilderPort in this file and avoid stale docs.

In `@worker/src/ports/ReasoningEnginePort.ts`:
- Line 2: The file header/comment currently references the old interface name
IReasoningEngine; update that comment to reference the new interface name
ReasoningEnginePort so the header matches the renamed interface (look for the
top-of-file comment and the interface declaration named ReasoningEnginePort) —
simply replace IReasoningEngine with ReasoningEnginePort in the header text to
keep documentation consistent with the code.

In `@worker/src/ports/TranscriptProviderPort.ts`:
- Line 2: The file's JSDoc still references the old interface name
ITranscriptProvider; update that comment to use the current interface name
TranscriptProviderPort so the documentation matches the code (search for the
JSDoc block at the top that mentions ITranscriptProvider and replace the name
with TranscriptProviderPort, ensuring any descriptions remain accurate).

In `@worker/src/services/LLMCascade.ts`:
- Line 4: Update the stale doc comment that mentions ILLMCascade to reference
the current interface LLMCascadePort: find the top-of-file comment in
LLMCascade.ts that starts "Implements ILLMCascade" and change the interface name
to "LLMCascadePort" so the documentation matches the implemented symbol
(LLMCascade class and its implemented interface LLMCascadePort).

In `@worker/src/services/PromptBuilder.ts`:
- Line 4: The top-of-file doc comment incorrectly references the old interface
name IPromptBuilder; update the comment to reference the current interface
PromptBuilderPort and any related identifiers (e.g., the class PromptBuilder and
the getUCISPrompt method) so the documentation matches the implementation;
ensure wording reflects that PromptBuilder implements PromptBuilderPort rather
than IPromptBuilder.

In `@worker/src/services/ReasoningEngine.ts`:
- Around line 5-10: Update the stale interface names in the top comment of
ReasoningEngine.ts: replace IReasoningEngine with ReasoningEnginePort,
IPromptBuilder with PromptBuilderPort, ILLMCascade with LLMCascadePort, and
IPersistenceRepository with PersistenceRepositoryPort; also ensure the listed
port methods (executeAndStream, execute) and the constructor DI list match the
actual symbols used in the file (the class constructor and methods
executeAndStream/execute) so the docblock reflects the current *Port naming and
method signatures.

In `@worker/src/services/TranscriptExtractor.ts`:
- Line 5: Update the stale documentation comment that references
ITranscriptProvider to the current port name TranscriptProviderPort; locate the
docblock above the TranscriptExtractor class (or where the comment appears) and
replace the old interface reference with TranscriptProviderPort and, if desired,
confirm the documented method/signature matches the actual port method
(fetch(videoId: string): Promise<string>).

In `@worker/src/services/UpstashCacheAdapter.ts`:
- Line 4: The doc comment at the top of UpstashCacheAdapter.ts incorrectly
references IPersistenceRepository; update that comment to reference the current
interface name PersistenceRepositoryPort (and optionally mention the
implementing class UpstashCacheAdapter) so the documentation matches the actual
implementation (class UpstashCacheAdapter implements PersistenceRepositoryPort).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 776c5915-bf59-40d3-a424-703e6dbf4f9a

📥 Commits

Reviewing files that changed from the base of the PR and between ba06a87 and 278b8fe.

📒 Files selected for processing (51)
  • .github/CONTRIBUTING.md
  • .github/SECURITY.md
  • LICENSE
  • docs/audit/10X_REAUDIT_IV_COMBINED_2026_06_10.md
  • docs/legal/LICENSE-ADDENDUM.md
  • docs/legal/NOTICE.md
  • docs/specs/IMPLEMENTATION_PLAN.md
  • package.json
  • web/app/api/analyses/persist/route.ts
  • web/app/api/analyses/route.ts
  • web/app/api/billing/webhook/route.ts
  • web/app/api/chat/conversations/[id]/messages/route.ts
  • web/app/api/chat/conversations/route.ts
  • web/app/api/chat/persist/route.ts
  • web/app/api/search/route.ts
  • web/hooks/useSSEStream.ts
  • web/lib/adapters/PostgresBillingAdapter.ts
  • web/lib/adapters/RedisTrafficAdapter.ts
  • web/lib/adapters/SettingsModelAdapter.ts
  • web/lib/adapters/StreamTokenAdapter.ts
  • web/lib/adapters/SupabaseAuthAdapter.ts
  • web/lib/adapters/SupabasePersistenceAdapter.ts
  • web/lib/adapters/WorkerIngestionAdapter.ts
  • web/lib/ports/AuthPort.ts
  • web/lib/ports/BillingQuotaPort.ts
  • web/lib/ports/ChatPersistencePort.ts
  • web/lib/ports/CryptographicTokenPort.ts
  • web/lib/ports/IIngestionPort.ts
  • web/lib/ports/IQuotaPort.ts
  • web/lib/ports/IngestionPort.ts
  • web/lib/ports/MetadataIngestionPort.ts
  • web/lib/ports/ModelResolutionPort.ts
  • web/lib/ports/PersistencePort.ts
  • web/lib/ports/QuotaPort.ts
  • web/lib/ports/TrafficGuardPort.ts
  • web/lib/ports/index.ts
  • web/lib/types/chat.ts
  • web/lib/usecases/CreateAnalysisUseCase.ts
  • web/package.json
  • worker/package.json
  • worker/src/ports/LLMCascadePort.ts
  • worker/src/ports/PersistenceRepositoryPort.ts
  • worker/src/ports/PromptBuilderPort.ts
  • worker/src/ports/ReasoningEnginePort.ts
  • worker/src/ports/TranscriptProviderPort.ts
  • worker/src/services/LLMCascade.ts
  • worker/src/services/PromptBuilder.ts
  • worker/src/services/ReasoningEngine.ts
  • worker/src/services/TranscriptExtractor.ts
  • worker/src/services/UpstashCacheAdapter.ts
  • worker/src/worker.ts
💤 Files with no reviewable changes (2)
  • web/lib/ports/IIngestionPort.ts
  • web/lib/ports/IQuotaPort.ts

Comment on lines +19 to +26
| **Root Version** | `1.4.1` | `package.json` |
| **Web Version** | `1.4.6` | `web/package.json` |
| **Worker Version**| `1.5.1` | `worker/package.json` |
| **Version Drift** | ⚠️ **CONFIRMED** | Monorepo versions (1.4.1 vs 1.4.6 vs 1.5.1) are out of sync |
| **Web LOC** | ~17,400 TS/TSX | Includes billing services and legal templates |
| **Worker LOC** | ~5,400 TS | Cloudflare Worker Hono code + chat streams |
| **DB Migrations** | 24 | From baseline to `20260607231000_c1_quota_auth_bypass.sql` |
| **pnpm Version** | `11.5.1` | `packageManager` drift vs `GEMINI.md` specified `11.1.3` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify version alignment across monorepo after this PR
echo "Root version:"
jq -r '.version' package.json

echo "Web version:"
jq -r '.version' web/package.json

echo "Worker version:"
jq -r '.version' worker/package.json

Repository: Hex-Tech-Lab/hex-yt-intel

Length of output: 130


Fix audit snapshot version metrics to match current monorepo state (all packages are 1.5.2)

  • docs/audit/10X_REAUDIT_IV_COMBINED_2026_06_10.md reports Root/Web/Worker versions as 1.4.1 / 1.4.6 / 1.5.1 with “Version Drift: ⚠️ CONFIRMED”, and marks H6 “UNCHANGED”, but the current repo has 1.5.2 in package.json, web/package.json, and worker/package.json—so the drift/H6 statements don’t align with the post-PR state.
  • Update the version metrics + H6 status in the doc (lines 19-26 / H6 section), or explicitly label the document as intentionally pinned to the pre-PR snapshot it’s describing.
🤖 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 `@docs/audit/10X_REAUDIT_IV_COMBINED_2026_06_10.md` around lines 19 - 26,
Update the audit snapshot table entries for "Root Version", "Web Version", and
"Worker Version" from 1.4.1 / 1.4.6 / 1.5.1 to 1.5.2, and change the "Version
Drift" / H6 status to reflect no drift (or explicitly note the doc is a pre-PR
snapshot) so the doc's metrics match the current monorepo state; edit the
markdown table row labels "Root Version", "Web Version", "Worker Version",
"Version Drift" and the H6 status text in the same document to perform this
correction.

Comment on lines +67 to +137
* **Target File**: [web/app/api/billing/webhook/route.ts](file:///home/kellyb_dev/projects/hex-yt-intel/web/app/api/billing/webhook/route.ts)
* **Action**: Restore the signature verification mechanism using `@paddle/paddle-node-sdk`. Gate the JSON parsing fallback strictly behind `process.env.NODE_ENV === 'development'`.
* **Estimated LLM Effort**: 15 minutes.

#### Step 1.2: Add AbortController to useSSEStream
* **Target File**: [web/hooks/useSSEStream.ts](file:///home/kellyb_dev/projects/hex-yt-intel/web/hooks/useSSEStream.ts)
* **Action**: Instantiate and maintain an `AbortController` in the SSE stream initiator. Call `.abort()` on the controller before starting a new stream or when the component unmounts to close old connections.
* **Estimated LLM Effort**: 20 minutes.

#### Step 1.3: Parallelize Traffic & Billing Checks in route.ts
* **Target File**: [web/app/api/analyses/route.ts](file:///home/kellyb_dev/projects/hex-yt-intel/web/app/api/analyses/route.ts)
* **Action**: Group `trafficAdapter.checkGate()` and `billingAdapter.checkGate()` calls into a single `Promise.all()` block. Saves ~100ms of latency per POST request.
* **Estimated LLM Effort**: 15 minutes.

---

### WAVE 2: Port Segregation (ISP/LSP Resolution)
* **Goal**: Refactor interfaces to contain only single-responsibility definitions.
* **Dependencies**: Wave 1 complete.

#### Step 2.1: Split IIngestionPort
* **Target Files**:
* [web/lib/ports/IIngestionPort.ts](file:///home/kellyb_dev/projects/hex-yt-intel/web/lib/ports/IIngestionPort.ts)
* [web/lib/adapters/WorkerIngestionAdapter.ts](file:///home/kellyb_dev/projects/hex-yt-intel/web/lib/adapters/WorkerIngestionAdapter.ts)
* **Action**:
* Create `IMetadataIngestionPort` containing only `fetch()` and `buildJobMetadata()`.
* Create `IModelResolutionPort` containing `resolveModels()`.
* Create `ICryptographicTokenPort` containing `signToken()`.
* Refactor `WorkerIngestionAdapter` to only implement `IMetadataIngestionPort`, removing the throwing methods.
* Move resolution and signing implementations into separate adapter classes cleanly.
* **Estimated LLM Effort**: 45 minutes.

#### Step 2.2: Split IQuotaPort
* **Target Files**:
* [web/lib/ports/IQuotaPort.ts](file:///home/kellyb_dev/projects/hex-yt-intel/web/lib/ports/IQuotaPort.ts)
* [web/lib/adapters/RedisTrafficAdapter.ts](file:///home/kellyb_dev/projects/hex-yt-intel/web/lib/adapters/RedisTrafficAdapter.ts)
* [web/lib/adapters/PostgresBillingAdapter.ts](file:///home/kellyb_dev/projects/hex-yt-intel/web/lib/adapters/PostgresBillingAdapter.ts)
* **Action**:
* Create `ITrafficGuardPort` with `checkGate()` for DDoS/rate-limiting.
* Create `IBillingQuotaPort` with `checkGate()` and `refund()` for credit transactions.
* Refactor `RedisTrafficAdapter` to implement `ITrafficGuardPort` (removing the unused `refund` method).
* Refactor `PostgresBillingAdapter` to implement `IBillingQuotaPort`.
* **Estimated LLM Effort**: 30 minutes.

---

### WAVE 3: Domain UseCase Isolation & Clean Routing
* **Goal**: Move business orchestration into UseCase classes and unify persistence.
* **Dependencies**: Wave 2 complete.

#### Step 3.1: Create CreateAnalysisUseCase
* **Target File**: `web/lib/usecases/CreateAnalysisUseCase.ts` (New File)
* **Action**: Write the core analysis creation workflow inside a constructor-configured class using the new segregated ports. Keep it completely free of HTTP inputs or Next.js routing parameters.
* **Estimated LLM Effort**: 30 minutes.

#### Step 3.2: Refactor route.ts POST Handler
* **Target File**: [web/app/api/analyses/route.ts](file:///home/kellyb_dev/projects/hex-yt-intel/web/app/api/analyses/route.ts)
* **Action**: Instantiate the adapters, pass them to `CreateAnalysisUseCase`, and execute the UseCase. Map resulting data to `NextResponse.json` payload shapes.
* **Estimated LLM Effort**: 20 minutes.

#### Step 3.3: Refactor route.ts GET Handler
* **Target File**: [web/app/api/analyses/route.ts](file:///home/kellyb_dev/projects/hex-yt-intel/web/app/api/analyses/route.ts)
* **Action**: Add a `findAnalysesByUserId(userId: string): Promise<CachedAnalysis[]>` method to [IPersistencePort](file:///home/kellyb_dev/projects/hex-yt-intel/web/lib/ports/IPersistencePort.ts). Query history through [SupabasePersistenceAdapter](file:///home/kellyb_dev/projects/hex-yt-intel/web/lib/adapters/SupabasePersistenceAdapter.ts) instead of writing inline database calls in the GET route.
* **Estimated LLM Effort**: 25 minutes.

#### Step 3.4: Reconcile Monorepo Versions
* **Target Files**:
* [package.json](file:///home/kellyb_dev/projects/hex-yt-intel/package.json)
* [web/package.json](file:///home/kellyb_dev/projects/hex-yt-intel/web/package.json)
* [worker/package.json](file:///home/kellyb_dev/projects/hex-yt-intel/worker/package.json)
* **Action**: Unify monorepo package versions to `1.5.2` (or current clean state) to complete the Housekeeping checks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Replace absolute file paths with relative paths for portability.

The implementation plan contains absolute file system paths (e.g., file:///home/kellyb_dev/projects/hex-yt-intel/web/app/api/billing/webhook/route.ts). These paths break portability and will fail when the repository is cloned to different machines or CI environments. Replace them with relative paths from the repository root.

📝 Example fix
-* **Target File**: [web/app/api/billing/webhook/route.ts](file:///home/kellyb_dev/projects/hex-yt-intel/web/app/api/billing/webhook/route.ts)
+* **Target File**: `web/app/api/billing/webhook/route.ts`

Apply this pattern to all file references in the document.

🧰 Tools
🪛 LanguageTool

[grammar] ~113-~113: Ensure spelling is correct
Context: ...*: 30 minutes. --- ### WAVE 3: Domain UseCase Isolation & Clean Routing * Goal: M...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~114-~114: Ensure spelling is correct
Context: ...oal**: Move business orchestration into UseCase classes and unify persistence. * **Depe...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.22.1)

[warning] 71-71: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 76-76: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 83-83: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 87-87: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 99-99: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 113-113: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 117-117: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 122-122: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 127-127: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 132-132: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 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 `@docs/specs/IMPLEMENTATION_PLAN.md` around lines 67 - 137, The document uses
absolute file URIs (strings starting with
"file:///home/kellyb_dev/projects/hex-yt-intel/") which breaks portability;
replace every absolute file:///... reference with a relative repo path (e.g.,
change "file:///home/.../web/app/api/billing/webhook/route.ts" to
"web/app/api/billing/webhook/route.ts") throughout IMPLEMENTATION_PLAN.md,
including all entries under Step 1.1, Step 1.2, Step 1.3, Step 2.1, Step 2.2 and
Wave 3 targets; search for the "file:///" prefix and update referenced paths to
their relative equivalents so links and targets like
web/app/api/analyses/route.ts, web/hooks/useSSEStream.ts,
web/lib/ports/IIngestionPort.ts, web/lib/adapters/WorkerIngestionAdapter.ts,
etc., no longer use absolute file URIs.

Comment on lines +67 to +75
const priorReport = (row.validationReport as any) || {};
const isInterrupted = status === 'interrupted';

// ADR 006: Dual-write - update both markdown and JSON payload columns
const { error: updateError } = await service
.from('analyses')
.update({
analysis_markdown: markdown,
analysis_payload: payload ?? null, // ADR 006: JSONB column for structured payload
model_used: model || 'edge-stream',
validation_passed: isInterrupted ? false : !!valid,
validation_report: {
...priorReport,
status: isInterrupted ? 'interrupted' : 'done',
model_used: model,
valid: isInterrupted ? false : !!valid,
},
updated_at: new Date().toISOString(),
})
.eq('id', analysisId);
const newReport = {
...priorReport,
status: isInterrupted ? 'interrupted' : 'done',
model_used: model,
valid: isInterrupted ? false : !!valid,
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Type cast to any and mixed naming conventions in validation report handling.

Line 67 casts row.validationReport to any, losing type safety. Additionally, the code accesses transcript_available (snake_case at line 95) from priorReport, suggesting the JSON blob uses snake_case while adapter row fields use camelCase. Consider defining a ValidationReport type to ensure consistency.

♻️ Suggested type definition
interface ValidationReport {
  status?: string;
  transcript_available?: boolean;
  model_used?: string;
  valid?: boolean;
  // ... other fields
}

const priorReport: ValidationReport = (row.validationReport as ValidationReport) || {};
🤖 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 `@web/app/api/analyses/persist/route.ts` around lines 67 - 75, The code loses
type safety by casting row.validationReport to any and mixes snake_case vs
camelCase in the JSON blob; define a ValidationReport interface (including
fields like status, transcript_available, model_used, valid, etc.), replace the
any cast so priorReport is typed as ValidationReport, and update usages around
priorReport/newReport (and the created newReport object) to use the typed
properties consistently (preserve existing snake_case fields if the stored JSON
uses them) so TypeScript enforces correct property names and types when
reading/writing validationReport.

url: validation.data.url,
timezone: validation.data.timezone || 'UTC',
forceRefresh: validation.data.forceRefresh || false,
explicitPersona: validation.data.persona as any,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Unsafe as any cast for persona parameter.

The as any cast bypasses type checking for the persona field. If validation.data.persona doesn't match PersonaId, runtime errors could occur in the use case.

🐛 Suggested fix
-      explicitPersona: validation.data.persona as any,
+      explicitPersona: validation.data.persona as PersonaId | undefined,

Or update AnalysisCreateSchema to use PersonaId type directly.

📝 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.

Suggested change
explicitPersona: validation.data.persona as any,
explicitPersona: validation.data.persona as PersonaId | undefined,
🤖 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 `@web/app/api/analyses/route.ts` at line 66, Replace the unsafe "as any" cast
for explicitPersona by ensuring validation.data.persona is typed/validated as
PersonaId: update AnalysisCreateSchema to declare persona as PersonaId (or
perform a runtime type guard) and then assign explicitPersona =
validation.data.persona (or cast to PersonaId only after validation); reference
the explicitPersona assignment and the AnalysisCreateSchema/PersonaId types to
locate and fix the code path where validation.data.persona is used.

Comment on lines +19 to +26
if (!secret && process.env.NODE_ENV === 'development') {
event = JSON.parse(body);
} else {
if (!secret) {
throw new Error('PADDLE_WEBHOOK_SECRET is required');
}
event = paddle.webhooks.unmarshal(body, secret, signature);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Development-mode signature bypass could be risky if NODE_ENV is misconfigured.

The fallback to JSON.parse when PADDLE_WEBHOOK_SECRET is missing in development mode is convenient, but if NODE_ENV is accidentally set to development in a production-like environment, webhook signature verification would be bypassed. Consider adding an explicit check or warning log.

🛡️ Suggested hardening
     if (!secret && process.env.NODE_ENV === 'development') {
+      console.warn('[Paddle Webhook] Signature verification BYPASSED - development mode without PADDLE_WEBHOOK_SECRET');
       event = JSON.parse(body);
     } else {
📝 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.

Suggested change
if (!secret && process.env.NODE_ENV === 'development') {
event = JSON.parse(body);
} else {
if (!secret) {
throw new Error('PADDLE_WEBHOOK_SECRET is required');
}
event = paddle.webhooks.unmarshal(body, secret, signature);
}
if (!secret && process.env.NODE_ENV === 'development') {
console.warn('[Paddle Webhook] Signature verification BYPASSED - development mode without PADDLE_WEBHOOK_SECRET');
event = JSON.parse(body);
} else {
if (!secret) {
throw new Error('PADDLE_WEBHOOK_SECRET is required');
}
event = paddle.webhooks.unmarshal(body, secret, signature);
}
🤖 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 `@web/app/api/billing/webhook/route.ts` around lines 19 - 26, The current
development-mode fallback parses the body without verification when secret is
missing, which is risky if NODE_ENV is misconfigured; update the webhook
handling in route.ts so that instead of silently using JSON.parse when secret is
falsey and NODE_ENV === 'development' you either (a) require an explicit opt-in
like a new env flag (e.g., DEV_ALLOW_UNVERIFIED_WEBHOOKS) and throw/log a clear
warning if that flag is not set, or (b) always throw and log an error unless
PADDLE_WEBHOOK_SECRET is present; specifically modify the block that references
secret, NODE_ENV, paddle.webhooks.unmarshal, signature and event to check for
PADDLE_WEBHOOK_SECRET and the opt-in flag, emit a prominent warning via your
logger when falling back, and avoid silently accepting unverified payloads.

Comment on lines +3 to +82
/**
* Handles all database operations for chat threads and messages, isolating the chat
* domains from the database engine.
*/
export interface ChatPersistencePort {
/**
* List conversations for a user.
*/
getConversations(userId: string): Promise<ChatConversation[]>;

/**
* Start a new chat conversation.
*/
createConversation(params: {
userId: string;
analysisId: string | null;
title: string;
}): Promise<ChatConversation>;

/**
* Retrieve a single conversation.
*/
getConversation(params: {
conversationId: string;
}): Promise<ChatConversation | null>;

/**
* Update the title of a conversation.
*/
updateConversationTitle(params: {
conversationId: string;
title: string;
}): Promise<void>;

/**
* Load history/messages for a thread.
*/
getMessages(params: {
conversationId: string;
}): Promise<ChatMessage[]>;

/**
* Check if a message already exists (idempotency support).
*/
findMessageByClientMsgId(params: {
conversationId: string;
clientMsgId: string;
}): Promise<ChatMessage | null>;

/**
* Save a chat message.
*/
createMessage(params: {
conversationId: string;
userId: string;
role: 'user' | 'assistant';
content: string;
clientMsgId?: string | null;
}): Promise<ChatMessage>;

/**
* Find the first assistant message created after a user message.
*/
findAssistantMessageAfter(params: {
conversationId: string;
timestamp: string;
}): Promise<ChatMessage | null>;

/**
* Retrieve analysis details for grounding the chat session.
*/
getAnalysisGrounding(params: {
analysisId: string;
}): Promise<{
title: string;
channelTitle: string | null;
analysisMarkdown: string | null;
status: string;
} | null>;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Move interface documentation to /docs/ directory.

The coding guidelines require documentation to be kept in /docs/, not in code comments. The JSDoc comments throughout this file should be extracted to architecture documentation.

🤖 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 `@web/lib/ports/ChatPersistencePort.ts` around lines 3 - 82, Remove the JSDoc
block comments from the ChatPersistencePort interface and its methods (leave the
exported interface and all method signatures intact) and move the explanatory
text into the project’s documentation directory as an architecture doc; create a
markdown entry that documents the ChatPersistencePort interface and each method
(ChatPersistencePort, getConversations, createConversation, getConversation,
updateConversationTitle, getMessages, findMessageByClientMsgId, createMessage,
findAssistantMessageAfter, getAnalysisGrounding) preserving the original
descriptions and request/response shapes so the code remains unchanged except
for removed comments.

Source: Coding guidelines

Comment on lines +3 to +24
/**
* Handles signing streaming access tokens bound to video, analysis job, and resolved models list.
*/
export interface CryptographicTokenPort {
/**
* Mint an HMAC-signed streaming token bound to videoId + analysisId + models.
*/
signAnalysisToken(params: {
videoId: string;
analysisId: string;
models: string[];
}): StreamToken;

/**
* Mint an HMAC-signed chat token bound to conversationId + userId + models.
*/
signChatToken(params: {
conversationId: string;
userId: string;
models: string[];
}): StreamToken;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Move interface documentation to /docs/ directory.

The coding guidelines require documentation to be kept in /docs/, not in code comments. The JSDoc comments at lines 3-5, 7-9, and 16-18 should be extracted to architecture documentation.

🤖 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 `@web/lib/ports/CryptographicTokenPort.ts` around lines 3 - 24, Remove the
inline JSDoc comments from the CryptographicTokenPort interface and its methods
signAnalysisToken and signChatToken and place that documentation into the
project architecture documentation (create a new docs entry describing the
interface purpose and the two method contracts: HMAC-signed streaming tokens
bound to their respective IDs and models). Update the code in
CryptographicTokenPort to keep only the type signatures (interface and method
params/return types) with no descriptive comments, and ensure the new docs
clearly mirror the removed comments so consumers can find the same information
outside the source file.

Source: Coding guidelines

Comment on lines +5 to +29
/**
* Handles only the retrieval of video metadata and transcripts, and persona detection.
*/
export interface MetadataIngestionPort {
/**
* Fetch video metadata and transcript in parallel.
* @throws When metadata fetch fails (caller must refund quota).
* @returns IngestionResult with transcript possibly empty.
*/
fetch(videoId: string): Promise<IngestionResult>;

/**
* Detect the target persona from video title + channel, or use the explicit override.
*/
detectPersona(params: {
title: string;
channelTitle: string;
explicitPersona?: PersonaId;
}): PersonaId;

/**
* Build the canonical AnalysisJobMetadata from raw video metadata.
*/
buildJobMetadata(metadata: VideoMetadata): AnalysisJobMetadata;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Move interface documentation to /docs/ directory.

The coding guidelines require documentation to be kept in /docs/, not in code comments. The JSDoc comments at lines 5-7, 9-13, 16-18, and 25-27 should be extracted to architecture documentation.

🤖 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 `@web/lib/ports/MetadataIngestionPort.ts` around lines 5 - 29, Remove the
inline JSDoc blocks from the MetadataIngestionPort interface and its members
(the top-level comment and the comments for fetch, detectPersona, and
buildJobMetadata) and instead add an entry in the project documentation
describing the interface responsibilities and contracts: list
MetadataIngestionPort, its methods fetch(videoId: string):
Promise<IngestionResult> (including the error/quotas behavior and that
transcript may be empty), detectPersona(params: {title, channelTitle,
explicitPersona?: PersonaId}): PersonaId, and buildJobMetadata(metadata:
VideoMetadata): AnalysisJobMetadata; keep type references (IngestionResult,
PersonaId, VideoMetadata, AnalysisJobMetadata) in the doc so implementers
understand expectations and remove the corresponding JSDoc comments from the
source.

Source: Coding guidelines

Comment on lines +111 to +136
/**
* Find analysis row for server-to-server persistence lookup.
*/
findAnalysisForPersist(params: {
analysisId: string;
videoId: string;
}): Promise<{
id: string;
userId: string;
title: string;
validationReport: any;
createdAt: string;
} | null>;

/**
* Update the analysis row with the final reasoning results.
*/
updateAnalysisResult(params: {
analysisId: string;
markdown: string;
payload: any;
model: string | null;
validationPassed: boolean;
status: 'done' | 'interrupted';
validationReport: any;
}): Promise<void>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Replace any types with proper type annotations.

Lines 121, 131, and 135 use any for validationReport and payload parameters, reducing type safety. Consider:

  • validationReport: Use a defined type (e.g., ValidationReportInput or a union with unknown)
  • payload: Use UCISPayloadV2 | null (already imported at line 3)
🔧 Suggested type improvements
   findAnalysisForPersist(params: {
     analysisId: string;
     videoId: string;
   }): Promise<{
     id: string;
     userId: string;
     title: string;
-    validationReport: any;
+    validationReport: ValidationReportInput | Record<string, unknown>;
     createdAt: string;
   } | null>;

   updateAnalysisResult(params: {
     analysisId: string;
     markdown: string;
-    payload: any;
+    payload: UCISPayloadV2 | null;
     model: string | null;
     validationPassed: boolean;
     status: 'done' | 'interrupted';
-    validationReport: any;
+    validationReport: Record<string, unknown>;
   }): Promise<void>;
🤖 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 `@web/lib/ports/PersistencePort.ts` around lines 111 - 136, The
findAnalysisForPersist and updateAnalysisResult method signatures use `any` for
`validationReport` and `payload`; update them to use concrete types: replace
`validationReport: any` with a proper type such as `ValidationReportInput` (or
`ValidationReportInput | unknown` if you need a safe fallback) in both the
findAnalysisForPersist return type and the updateAnalysisResult parameter, and
change `payload: any` in updateAnalysisResult to `payload: UCISPayloadV2 | null`
(UCISPayloadV2 is already imported) so both methods use explicit, safer types
instead of `any`.

Comment on lines +25 to +28
export type UseCaseResult =
| { type: 'cache_hit'; data: any; headers?: Record<string, string> }
| { type: 'processing'; data: any; headers?: Record<string, string> }
| { type: 'error'; code: string; status: number; message: string; headers?: Record<string, string> };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Strengthen typing for UseCaseResult.data field.

Using any for the data field loses type safety and makes it harder to catch contract mismatches between the use case and route handlers. Consider defining specific data shapes for each result type.

♻️ Suggested type-safe approach
+interface CacheHitData {
+  id: string;
+  analysisId: string;
+  videoId: string;
+  status: 'done';
+  title: string;
+  markdown: string;
+  // ... other fields
+}
+
+interface ProcessingData {
+  id: string;
+  analysisId: string;
+  videoId: string;
+  status: 'processing';
+  stream: { url: string; sig: string; exp: number };
+  // ... other fields
+}
+
 export type UseCaseResult =
-  | { type: 'cache_hit'; data: any; headers?: Record<string, string> }
-  | { type: 'processing'; data: any; headers?: Record<string, string> }
+  | { type: 'cache_hit'; data: CacheHitData; headers?: Record<string, string> }
+  | { type: 'processing'; data: ProcessingData; headers?: Record<string, string> }
   | { type: 'error'; code: string; status: number; message: string; headers?: Record<string, string> };
📝 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.

Suggested change
export type UseCaseResult =
| { type: 'cache_hit'; data: any; headers?: Record<string, string> }
| { type: 'processing'; data: any; headers?: Record<string, string> }
| { type: 'error'; code: string; status: number; message: string; headers?: Record<string, string> };
interface CacheHitData {
id: string;
analysisId: string;
videoId: string;
status: 'done';
title: string;
markdown: string;
// ... other fields
}
interface ProcessingData {
id: string;
analysisId: string;
videoId: string;
status: 'processing';
stream: { url: string; sig: string; exp: number };
// ... other fields
}
export type UseCaseResult =
| { type: 'cache_hit'; data: CacheHitData; headers?: Record<string, string> }
| { type: 'processing'; data: ProcessingData; headers?: Record<string, string> }
| { type: 'error'; code: string; status: number; message: string; headers?: Record<string, string> };
🤖 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 `@web/lib/usecases/CreateAnalysisUseCase.ts` around lines 25 - 28, The
UseCaseResult type uses `any` for the `data` field which erases type safety;
replace `any` with explicit interfaces for each discriminated branch (e.g.,
define CacheHitData, ProcessingData and use those in the `UseCaseResult` union)
so callers of the UseCase (and route handlers) can rely on concrete shapes;
update the exported `UseCaseResult` union to reference these new types for
`data` (and add or tighten optional `headers` types if needed) and update
impacted call sites to conform to the new data shapes (look for references to
UseCaseResult, cache_hit, and processing handling).

@TechHypeXP
TechHypeXP merged commit fc0d270 into main Jun 10, 2026
14 of 16 checks passed
@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

TechHypeXP pushed a commit that referenced this pull request Jul 23, 2026
…, 16 moderate, 1 low)

Queried the Dependabot API directly (gh api repos/.../dependabot/alerts) for
exact vulnerable-version ranges and first_patched_version per alert, rather
than guessing at bump targets from severity labels alone.

- next: 16.2.6 -> 16.2.11 (web/package.json). Single patch-level bump
  resolves all 16 alerts (#73-90) at once -- SSRF in Server Actions/rewrites,
  Middleware/Turbopack bypass, DoS in Server Actions and Image Optimization
  SVGs, cache confusion on invalid-UTF8 request bodies, unauthenticated
  Server Function endpoint disclosure, unbounded Server Action payload in
  Edge runtime. Same minor line (16.2.x), no breaking changes -- verified via
  full build + 861-test suite, not assumed safe from the version number alone.
- tar: ^7.5.16 -> ^7.5.19 (pnpm-workspace.yaml override). Resolves 4 cascading
  CVEs at once (#62 moderate through #64 critical -- decompression/parse DoS
  via unlimited input, negative-entry-size infinite loop, PAX numeric path
  type confusion, NUL-byte DoS).
- js-yaml: ^4.1.2 -> ^4.3.0 (override). #61: merge-key chains forcing
  quadratic CPU consumption.
- fast-uri: new override ^3.1.4 (was resolving to 3.1.2, no prior override
  entry). #69, #72: host confusion via failed IDN canonicalization and via a
  literal backslash authority delimiter.
- sharp: new override ^0.35.0 (transitive, no direct dependency anywhere in
  the workspace). #71: inherited libvips CVEs (2026-33327/33328/35590/35591).
- hono: new override ^4.12.27 (root workspace only -- video-pipelines own
  lockfile already resolved 4.12.31, already patched, no action needed
  there). #66-68: XSS via cx() escaping bypass, jsx context leaking across
  requests, API Gateway v1 adapter dropping repeated header values.
- dompurify: ^3.4.11 -> ^3.4.12 (override, was one patch version short).
  #70: CUSTOM_ELEMENT_HANDLING bypasses afterSanitizeElements.
- @hono/node-server: ^1.13.7 -> ^2.0.5 (video-pipeline/package.json). #91-92:
  Windows path traversal in serve-static via encoded backslash (%5C) --
  these two were NET NEW from tonights video-pipeline addition, not
  pre-existing debt. A major version bump (1.x->2.x) for a service that only
  calls serve({fetch, port}) minimally -- verified beyond typecheck: full
  Docker build (npm audit inside the build reported "found 0
  vulnerabilities"), then ran the container and confirmed /health and the
  auth-gate 401 both still work correctly post-bump.

Duplicate-key fix along the way: pnpm-workspace.yaml already had tar and
js-yaml override entries further down the file that my first pass missed,
causing two separate "duplicated mapping key" YAML errors on `pnpm install`
-- caught immediately (install refuses to run on invalid YAML, not a silent
failure) and consolidated onto the pre-existing entries instead of leaving
two competing pins for the same package.

Verified E2E, not just typecheck, given the scope of what changed:
- web: type-check clean, lint clean (0/0), production build clean, full
  vitest suite 861 passed / 16 pre-existing skips / 0 failed.
- worker: typecheck clean, esbuild clean (2.1mb, no size regression).
- video-pipeline: typecheck clean, Docker build clean (0 vulnerabilities per
  npm audit inside the image), container run-and-curl verified /health
  (all 3 binaries report healthy) and the 401 auth gate, matching pre-bump
  behavior exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TechHypeXP pushed a commit that referenced this pull request Jul 31, 2026
…, 16 moderate, 1 low)

Queried the Dependabot API directly (gh api repos/.../dependabot/alerts) for
exact vulnerable-version ranges and first_patched_version per alert, rather
than guessing at bump targets from severity labels alone.

- next: 16.2.6 -> 16.2.11 (web/package.json). Single patch-level bump
  resolves all 16 alerts (#73-90) at once -- SSRF in Server Actions/rewrites,
  Middleware/Turbopack bypass, DoS in Server Actions and Image Optimization
  SVGs, cache confusion on invalid-UTF8 request bodies, unauthenticated
  Server Function endpoint disclosure, unbounded Server Action payload in
  Edge runtime. Same minor line (16.2.x), no breaking changes -- verified via
  full build + 861-test suite, not assumed safe from the version number alone.
- tar: ^7.5.16 -> ^7.5.19 (pnpm-workspace.yaml override). Resolves 4 cascading
  CVEs at once (#62 moderate through #64 critical -- decompression/parse DoS
  via unlimited input, negative-entry-size infinite loop, PAX numeric path
  type confusion, NUL-byte DoS).
- js-yaml: ^4.1.2 -> ^4.3.0 (override). #61: merge-key chains forcing
  quadratic CPU consumption.
- fast-uri: new override ^3.1.4 (was resolving to 3.1.2, no prior override
  entry). #69, #72: host confusion via failed IDN canonicalization and via a
  literal backslash authority delimiter.
- sharp: new override ^0.35.0 (transitive, no direct dependency anywhere in
  the workspace). #71: inherited libvips CVEs (2026-33327/33328/35590/35591).
- hono: new override ^4.12.27 (root workspace only -- video-pipelines own
  lockfile already resolved 4.12.31, already patched, no action needed
  there). #66-68: XSS via cx() escaping bypass, jsx context leaking across
  requests, API Gateway v1 adapter dropping repeated header values.
- dompurify: ^3.4.11 -> ^3.4.12 (override, was one patch version short).
  #70: CUSTOM_ELEMENT_HANDLING bypasses afterSanitizeElements.
- @hono/node-server: ^1.13.7 -> ^2.0.5 (video-pipeline/package.json). #91-92:
  Windows path traversal in serve-static via encoded backslash (%5C) --
  these two were NET NEW from tonights video-pipeline addition, not
  pre-existing debt. A major version bump (1.x->2.x) for a service that only
  calls serve({fetch, port}) minimally -- verified beyond typecheck: full
  Docker build (npm audit inside the build reported "found 0
  vulnerabilities"), then ran the container and confirmed /health and the
  auth-gate 401 both still work correctly post-bump.

Duplicate-key fix along the way: pnpm-workspace.yaml already had tar and
js-yaml override entries further down the file that my first pass missed,
causing two separate "duplicated mapping key" YAML errors on `pnpm install`
-- caught immediately (install refuses to run on invalid YAML, not a silent
failure) and consolidated onto the pre-existing entries instead of leaving
two competing pins for the same package.

Verified E2E, not just typecheck, given the scope of what changed:
- web: type-check clean, lint clean (0/0), production build clean, full
  vitest suite 861 passed / 16 pre-existing skips / 0 failed.
- worker: typecheck clean, esbuild clean (2.1mb, no size regression).
- video-pipeline: typecheck clean, Docker build clean (0 vulnerabilities per
  npm audit inside the image), container run-and-curl verified /health
  (all 3 binaries report healthy) and the 401 auth gate, matching pre-bump
  behavior exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants