Skip to content

fix: harden telemetry failure and source projection - #3

Merged
austinintelligence merged 1 commit into
mainfrom
codex/perspectica-telemetry-fix
Aug 3, 2026
Merged

austinintelligence merged 1 commit into
mainfrom
codex/perspectica-telemetry-fix

Conversation

@austinintelligence

@austinintelligence austinintelligence commented Aug 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Preserve the underlying adjudication/provider failure instead of masking it with an invalid empty terminal-event message.
  • Make evidence adjudication bounded and tolerant of omitted nullable fields.
  • Exclude social, promotional, same-publication, and navigation links from Works Cited while retaining external and likely-primary sources.

Root cause

The supplied telemetry reached the pipeline catch path with an empty Error.message. The catch emitted that empty value, and PipelineEventSchema.parse then failed on the required nonempty data.message, masking the original failure. The pipeline now guarantees a bounded fallback message.

Validation

  • pnpm format:check
  • pnpm typecheck
  • pnpm test — 26 files, 109 tests
  • pnpm --filter @perspectica/extension build
  • pnpm bench:v2
  • git diff --check
  • Desktop unpacked extension refreshed and byte-matched against the build: 15 files, 1,143,059 bytes, 0 differences

No new live authenticated provider analysis was run in this environment; the failure handling fix is directly based on the supplied Fox News telemetry.

Summary by CodeRabbit

  • New Features
    • Improved article link classification for social, promotional, and primary-source links.
    • Evidence adjudication now processes results in smaller batches and returns more focused decisions.
  • Bug Fixes
    • Missing evidence fields now safely default to null.
    • Pipeline failures provide clearer, bounded error messages.
    • Source reports now exclude same-publication links and focus on external or likely-primary sources.
  • Tests
    • Added coverage for link classification and pipeline failure handling.

Copilot AI review requested due to automatic review settings August 3, 2026 15:50
@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Article link classification

Layer / File(s) Summary
Classify social and promotional links
packages/extraction/src/article-index.ts, packages/extraction/src/article-index.test.ts
Article indexing recognizes more social, non-editorial, and promotional links. Tests cover representative classifications.

Intelligence pipeline updates

Layer / File(s) Summary
Bounded evidence adjudication
packages/contracts/src/evidence.ts, packages/intelligence/src/evidence/adjudication.ts
Nullable adjudication fields default to null. Adjudication uses shorter prompts, batches of six candidates, and a maximum of eight decisions.
Normalize pipeline failure messages
packages/intelligence/src/pipeline.ts, packages/intelligence/src/pipeline.test.ts
Pipeline failures now use fallback and length-limited messages. Tests cover empty errors.
Project external source links
packages/intelligence/src/report/projector.ts, packages/intelligence/src/report/projector.test.ts
Source projection now includes only external and likely-primary links.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CandidateEvidence
  participant adjudicateEvidence
  participant Adjudicator
  CandidateEvidence->>adjudicateEvidence: candidate batches of up to six
  adjudicateEvidence->>Adjudicator: truncated batch prompt
  Adjudicator-->>adjudicateEvidence: batch decisions
  adjudicateEvidence-->>CandidateEvidence: aggregated decisions
Loading

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 title clearly summarizes the two primary changes: telemetry failure handling and source projection.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/perspectica-telemetry-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@austinintelligence
austinintelligence merged commit ab50cb2 into main Aug 3, 2026
3 of 4 checks passed
@austinintelligence
austinintelligence deleted the codex/perspectica-telemetry-fix branch August 3, 2026 15:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the intelligence pipeline’s terminal failure handling to avoid masking underlying errors, tightens evidence adjudication constraints, and refines “Works Cited” source projection to exclude non-editorial link types.

Changes:

  • Add a bounded fallback message for terminal pipeline failures when an upstream error message is empty.
  • Make evidence adjudication more tolerant (nullable defaults) and more constrained (smaller output + batched adjudication calls).
  • Exclude same-publication links from the projected source list (keeping only external and likely-primary sources).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/intelligence/src/report/projector.ts Updates source projection to include only external and likely-primary links.
packages/intelligence/src/report/projector.test.ts Updates expected projected sources to match the new filtering.
packages/intelligence/src/pipeline.ts Adds pipelineErrorMessage to ensure terminal failure events always have a valid bounded message.
packages/intelligence/src/pipeline.test.ts Adds coverage for empty-error provider failures producing a valid terminal failure event.
packages/intelligence/src/evidence/adjudication.ts Reduces adjudication output size and introduces batching for adjudication calls.
packages/extraction/src/article-index.ts Expands link classification to better label social/promotional/non-editorial hosts.
packages/extraction/src/article-index.test.ts Adds tests ensuring social/promotional links are classified correctly.
packages/contracts/src/evidence.ts Makes nullable adjudication fields default to null when omitted.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +115 to +119
const maxCandidatesPerCall = 6;
const decisions: EvidenceAdjudication[] = [];
for (let offset = 0; offset < input.candidates.length; offset += maxCandidatesPerCall) {
const batch = input.candidates.slice(offset, offset + maxCandidatesPerCall);
decisions.push(

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/intelligence/src/pipeline.test.ts (1)

178-199: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the new normalization branches.

This test covers only an empty Error during analysis. Add cases for a thrown string, a whitespace-only message, a message longer than 1,000 characters, and a targeted-retry failure.

🤖 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 `@packages/intelligence/src/pipeline.test.ts` around lines 178 - 199, Extend
the analysis failure tests around analyzeArticle to cover normalization of a
thrown string, a whitespace-only Error message, and an Error message exceeding
1,000 characters, asserting each produces the expected terminal failure message.
Also add a targeted-retry failure case using the existing retry configuration or
symbols, and verify its normalized failure event behavior.
🤖 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 `@packages/extraction/src/article-index.ts`:
- Line 207: Update the non-editorial host check in the link classification logic
around NON_EDITORIAL_HOSTS and PROMOTIONAL_PATTERN so it matches both each
configured apex domain and its subdomains, consistent with the SOCIAL_HOSTS
matching behavior. Ensure hosts such as www.beyondwords.io are treated as
non-editorial before they can enter the external-source projection.

In `@packages/intelligence/src/evidence/adjudication.ts`:
- Line 13: Update the aggregation logic in adjudicator.adjudicate so the public
result is capped at eight decisions, not just each individual batch response.
After combining batch results, apply a deterministic selection rule (preserving
the established decision order) before returning the aggregate, and add a
regression test covering multiple batches that would otherwise exceed the limit.
- Around line 115-126: Update adjudicateEvidence to compute one absolute
deadline from input.budget.totalDeadlineMs, derive the remaining time before
each batch, and pass a budget with totalDeadlineMs clamped to that remaining
duration into input.adjudicator.adjudicate. Preserve the existing six-candidate
batching and decision aggregation while ensuring every batch and retry shares
the pipeline deadline.

---

Nitpick comments:
In `@packages/intelligence/src/pipeline.test.ts`:
- Around line 178-199: Extend the analysis failure tests around analyzeArticle
to cover normalization of a thrown string, a whitespace-only Error message, and
an Error message exceeding 1,000 characters, asserting each produces the
expected terminal failure message. Also add a targeted-retry failure case using
the existing retry configuration or symbols, and verify its normalized failure
event behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 025b6cf4-7888-49f0-920b-367c4b722b08

📥 Commits

Reviewing files that changed from the base of the PR and between 9a5bcd5 and 42ce9c7.

📒 Files selected for processing (8)
  • packages/contracts/src/evidence.ts
  • packages/extraction/src/article-index.test.ts
  • packages/extraction/src/article-index.ts
  • packages/intelligence/src/evidence/adjudication.ts
  • packages/intelligence/src/pipeline.test.ts
  • packages/intelligence/src/pipeline.ts
  • packages/intelligence/src/report/projector.test.ts
  • packages/intelligence/src/report/projector.ts

return "social";
}
if (/\b(?:subscribe|newsletter|advert|sponsor|shop|store)\b/i.test(`${link.label} ${link.url}`)) {
if (NON_EDITORIAL_HOSTS.has(linkHost) || PROMOTIONAL_PATTERN.test(`${link.label} ${link.url}`)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Match non-editorial host subdomains.

NON_EDITORIAL_HOSTS.has(linkHost) only matches beyondwords.io. A link such as https://www.beyondwords.io/... classifies as external and can reach Works Cited through the external-source projection. Match the apex domain and its subdomains, as SOCIAL_HOSTS already does.

Proposed fix
-  if (NON_EDITORIAL_HOSTS.has(linkHost) || PROMOTIONAL_PATTERN.test(`${link.label} ${link.url}`)) {
+  if (
+    [...NON_EDITORIAL_HOSTS].some(
+      (domain) => linkHost === domain || linkHost.endsWith(`.${domain}`),
+    ) ||
+    PROMOTIONAL_PATTERN.test(`${link.label} ${link.url}`)
+  ) {
📝 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 (NON_EDITORIAL_HOSTS.has(linkHost) || PROMOTIONAL_PATTERN.test(`${link.label} ${link.url}`)) {
if (
[...NON_EDITORIAL_HOSTS].some(
(domain) => linkHost === domain || linkHost.endsWith(`.${domain}`),
) ||
PROMOTIONAL_PATTERN.test(`${link.label} ${link.url}`)
) {
🤖 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 `@packages/extraction/src/article-index.ts` at line 207, Update the
non-editorial host check in the link classification logic around
NON_EDITORIAL_HOSTS and PROMOTIONAL_PATTERN so it matches both each configured
apex domain and its subdomains, consistent with the SOCIAL_HOSTS matching
behavior. Ensure hosts such as www.beyondwords.io are treated as non-editorial
before they can enter the external-source projection.


const AdjudicationOutputSchema = z.object({
decisions: z.array(EvidenceAdjudicationSchema).max(96),
decisions: z.array(EvidenceAdjudicationSchema).max(8),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Enforce the eight-decision limit on the public result.

Line 13 limits each adjudicator.adjudicate response. Lines 115-126 append every batch response and return the aggregate without a cap. Multiple batches can therefore return more than eight decisions, which conflicts with the bounded-output objective.

Apply one aggregate limit and a deterministic selection rule. Add a regression test with multiple batches.

Suggested aggregate limit
+const MAX_DECISIONS = 8;
+
 const AdjudicationOutputSchema = z.object({
-  decisions: z.array(EvidenceAdjudicationSchema).max(8),
+  decisions: z.array(EvidenceAdjudicationSchema).max(MAX_DECISIONS),
 });
...
-  return decisions;
+  return decisions.slice(0, MAX_DECISIONS);

Also applies to: 115-126

🤖 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 `@packages/intelligence/src/evidence/adjudication.ts` at line 13, Update the
aggregation logic in adjudicator.adjudicate so the public result is capped at
eight decisions, not just each individual batch response. After combining batch
results, apply a deterministic selection rule (preserving the established
decision order) before returning the aggregate, and add a regression test
covering multiple batches that would otherwise exceed the limit.

Comment on lines +115 to +126
const maxCandidatesPerCall = 6;
const decisions: EvidenceAdjudication[] = [];
for (let offset = 0; offset < input.candidates.length; offset += maxCandidatesPerCall) {
const batch = input.candidates.slice(offset, offset + maxCandidatesPerCall);
decisions.push(
...(await input.adjudicator.adjudicate({
...input,
candidates: batch,
})),
);
}
return decisions;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 8 \
  -e '\btotalDeadlineMs\b' \
  -e '\bAbortController\b' \
  -e '\bdeadline\b' \
  -e '\badjudicateEvidence\b' \
  packages/intelligence packages/contracts --glob '*.ts'

Repository: drperky20/perspectica

Length of output: 18822


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== files =="
git ls-files 'packages/intelligence/src/**/*.ts' | sed -n '1,120p'

echo
echo "== adjudication.ts outline =="
ast-grep outline packages/intelligence/src/evidence/adjudication.ts --view expanded || true

echo
echo "== adjudication.ts 1-150 =="
cat -n packages/intelligence/src/evidence/adjudication.ts | sed -n '1,150p'

echo
echo "== budgets.ts =="
cat -n packages/intelligence/src/budgets.ts | sed -n '1,120p'

echo
echo "== exact pipeline/adjudicator usages =="
rg -n -C 4 'adjudicateEvidence|budget: resolveAnalysisBudget|resolveAnalysisBudget\(' packages/intelligence/src --glob '*.ts'

Repository: drperky20/perspectica

Length of output: 15415


Carry a remaining deadline for each adjudication batch.

adjudicateEvidence creates 6-candidate batches and passes the same input.budget into each batch. createModelEvidenceAdjudicator uses input.budget.totalDeadlineMs for every generateText timeout, so multiple batches plus one retry each can exceed the pipeline deadline. Track a single absolute deadline and clamp each batch timeout to the remaining time before passing it to adjudicator.adjudicate.

🤖 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 `@packages/intelligence/src/evidence/adjudication.ts` around lines 115 - 126,
Update adjudicateEvidence to compute one absolute deadline from
input.budget.totalDeadlineMs, derive the remaining time before each batch, and
pass a budget with totalDeadlineMs clamped to that remaining duration into
input.adjudicator.adjudicate. Preserve the existing six-candidate batching and
decision aggregation while ensuring every batch and retry shares the pipeline
deadline.

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