-
Notifications
You must be signed in to change notification settings - Fork 0
fix: harden telemetry failure and source projection #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import { ArticleDocumentSchema } from "@perspectica/contracts"; | ||
| import { describe, expect, it } from "vitest"; | ||
| import { buildArticleIndex } from "./article-index"; | ||
|
|
||
| describe("ArticleIndex link classification", () => { | ||
| it("keeps sharing, embedded-audio, and app links out of editorial sources", () => { | ||
| const article = ArticleDocumentSchema.parse({ | ||
| fingerprint: "article-links", | ||
| canonicalUrl: "https://www.foxnews.com/story", | ||
| title: "A story", | ||
| author: "Reporter", | ||
| publication: "Fox News", | ||
| publishedAt: null, | ||
| language: "en", | ||
| contentType: "news", | ||
| paragraphs: [ | ||
| { | ||
| id: "p1", | ||
| index: 0, | ||
| kind: "paragraph", | ||
| speaker: null, | ||
| text: "The report described the event and cited an outside record.", | ||
| }, | ||
| ], | ||
| links: [ | ||
| { | ||
| id: "flipboard", | ||
| label: "Flipboard", | ||
| url: "https://share.flipboard.com/bookmarklet/popout?url=https%3A%2F%2Fwww.foxnews.com%2Fstory", | ||
| paragraphId: null, | ||
| }, | ||
| { | ||
| id: "beyondwords", | ||
| label: "beyondwords.io", | ||
| url: "https://beyondwords.io/player/example", | ||
| paragraphId: null, | ||
| }, | ||
| { | ||
| id: "app", | ||
| label: "CLICK HERE TO DOWNLOAD THE FOX NEWS APP", | ||
| url: "https://foxnews.onelink.me/example", | ||
| paragraphId: null, | ||
| }, | ||
| { | ||
| id: "primary", | ||
| label: "Public record", | ||
| url: "https://example.gov/record", | ||
| paragraphId: "p1", | ||
| }, | ||
| ], | ||
| extraction: { | ||
| extractorVersion: "test", | ||
| extractedAt: "2026-08-03T00:00:00.000Z", | ||
| wordCount: 10, | ||
| articleStatus: "article", | ||
| contentChars: 62, | ||
| contentTruncated: false, | ||
| }, | ||
| }); | ||
|
|
||
| const index = buildArticleIndex(article); | ||
| expect(Object.fromEntries(index.links.map((link) => [link.id, link.classification]))).toEqual({ | ||
| flipboard: "social", | ||
| beyondwords: "promotional", | ||
| app: "promotional", | ||
| primary: "likely-primary", | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,7 @@ import type { AnalysisPlan } from "@perspectica/contracts/report"; | |
| import type { AnalysisBudget } from "../budgets"; | ||
|
|
||
| const AdjudicationOutputSchema = z.object({ | ||
| decisions: z.array(EvidenceAdjudicationSchema).max(96), | ||
| decisions: z.array(EvidenceAdjudicationSchema).max(8), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Enforce the eight-decision limit on the public result. Line 13 limits each 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 |
||
| }); | ||
|
|
||
| export interface EvidenceAdjudicationInput { | ||
|
|
@@ -51,7 +51,7 @@ function buildPrompt(input: EvidenceAdjudicationInput): string { | |
| .slice(0, Math.max(input.budget.maxSources * 4, 24)) | ||
| .map( | ||
| (candidate) => | ||
| `CANDIDATE ${candidate.id} mission=${candidate.missionId ?? "global-search"} url=${candidate.sourceUrl} title=${compact(candidate.title, 300)} kind=${candidate.contentKind} sourceType=${candidate.sourceType}\nCONTENT: ${compact(candidate.content, 4_000)}\nDISCOVERY: ${compact(candidate.discoveryContext ?? "", 2_000)}`, | ||
| `CANDIDATE ${candidate.id} mission=${candidate.missionId ?? "global-search"} url=${candidate.sourceUrl} title=${compact(candidate.title, 300)} kind=${candidate.contentKind} sourceType=${candidate.sourceType}\nCONTENT: ${compact(candidate.content, 2_400)}\nDISCOVERY: ${compact(candidate.discoveryContext ?? "", 600)}`, | ||
| ) | ||
| .join("\n\n"); | ||
| return [ | ||
|
|
@@ -73,6 +73,7 @@ const SYSTEM_PROMPT = [ | |
| "Supports, contradicts, and qualifies require an exact planned claim and a clear source-content anchor.", | ||
| "Use context only for journalist-work, publication-history, comparable-coverage, or topic-context that is explicitly present in the candidate.", | ||
| "Return no decision for irrelevant, ambiguous, self-referential, or discovery-only candidates. Do not write generic discovery prose such as 'surfaced a relevant source'.", | ||
| "Return at most one short decision per candidate. Keep source excerpts contiguous and as short as possible (preferably under 600 characters).", | ||
| "Every candidateId and missionId must be copied exactly from the input. Do not invent IDs.", | ||
| ].join(" "); | ||
|
|
||
|
|
@@ -111,5 +112,16 @@ export async function adjudicateEvidence( | |
| input: EvidenceAdjudicationInput & { adjudicator?: EvidenceAdjudicator }, | ||
| ): Promise<EvidenceAdjudication[]> { | ||
| if (!input.adjudicator || input.candidates.length === 0) return []; | ||
| return input.adjudicator.adjudicate(input); | ||
| 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( | ||
|
Comment on lines
+115
to
+119
|
||
| ...(await input.adjudicator.adjudicate({ | ||
| ...input, | ||
| candidates: batch, | ||
| })), | ||
| ); | ||
| } | ||
| return decisions; | ||
|
Comment on lines
+115
to
+126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 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.
🤖 Prompt for AI Agents |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Match non-editorial host subdomains.
NON_EDITORIAL_HOSTS.has(linkHost)only matchesbeyondwords.io. A link such ashttps://www.beyondwords.io/...classifies asexternaland can reach Works Cited through the external-source projection. Match the apex domain and its subdomains, asSOCIAL_HOSTSalready does.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents