feat: APEX report ingester — research provenance queryable in-product#67
feat: APEX report ingester — research provenance queryable in-product#67djimit wants to merge 1 commit into
Conversation
ApexReportService reads the OpenMythos APEX research reports (APEX_R<round>_<slug>.md, configured via OPENMYTHOS_REPORTS_PATH) with format-tolerant parsing: round from the filename, title from the first heading, decision from an inline `Decision:` line or a `## Decision` section. Rounds can carry multiple reports. Routes: GET /api/openmythos/apex/reports (index, newest round first), GET /api/openmythos/apex/reports/:round (bodies). Verified live against the real corpus: 73 reports across 43 rounds parsed, decisions extracted through all three format generations (R1 null, R16 section-style 'promoted', R44 inline 'negative_result'). Dashboard surface deliberately deferred until #60 merges (both would touch the same App/Layout regions). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Kilo Code Review could not run — your account is out of credits. Add credits or switch to a free model to enable reviews on this change. |
There was a problem hiding this comment.
Code Review
This pull request introduces the ApexReportService to ingest and parse OpenMythos APEX research reports from markdown files, exposing them via new API endpoints (/apex/reports and /apex/reports/:round) and adding corresponding unit tests. Feedback focuses on optimizing performance by caching the parsed report summaries in memory to avoid blocking the Node.js event loop with synchronous disk I/O on every request. Additionally, a bug was identified in the regular expression used to parse decisions, where leading hyphens or list bullets could be incorrectly matched; a more robust, anchored regex was suggested to resolve this.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| export class ApexReportService { | ||
| constructor(private reportsPath: string = process.env.OPENMYTHOS_REPORTS_PATH || '') {} |
There was a problem hiding this comment.
The list() method synchronously reads and parses all files in the reports directory on every single call. Since these reports are static research files, doing synchronous I/O (readdirSync, readFileSync, statSync) on every API request will block the Node.js event loop and severely degrade server performance under load.
We should cache the parsed summaries in memory after the first load to avoid redundant disk I/O.
export class ApexReportService {
private cachedSummaries: ApexReportSummary[] | null = null;
constructor(private reportsPath: string = process.env.OPENMYTHOS_REPORTS_PATH || '') {}| for (let j = i + 1; j < lines.length; j++) { | ||
| const next = lines[j].trim(); | ||
| if (!next) continue; | ||
| const word = next.match(/`?([A-Za-z_-]+)`?/); |
There was a problem hiding this comment.
The current regular expression /?([A-Za-z_-]+)?/ has a bug when parsing decision lines that start with list bullets (e.g., - \promoted`). Because the hyphen -is included in the character class[A-Za-z_-], the regex will match the leading hyphen instead of the actual decision word, resulting in decisionbeing parsed as"-"`.
Additionally, because the regex is unanchored, it can match arbitrary words in a sentence or even subsequent section headers (like ## Next Section).
Using an anchored regular expression that explicitly accounts for optional leading list markers, backticks, and trailing punctuation resolves this safely.
| const word = next.match(/`?([A-Za-z_-]+)`?/); | |
| const word = next.match(/^(?:Decision:\\s*)?(?:[-*\\s]+)?\\x60?([A-Za-z_-]+)\\x60?[.\\s]*$/); |
| list(): ApexReportSummary[] { | ||
| if (!this.reportsPath) return []; | ||
| let entries: string[]; | ||
| try { entries = readdirSync(this.reportsPath); } catch { return []; } |
There was a problem hiding this comment.
Update the list() method to return the cached summaries if they have already been loaded, and populate the cache on the first run.
| list(): ApexReportSummary[] { | |
| if (!this.reportsPath) return []; | |
| let entries: string[]; | |
| try { entries = readdirSync(this.reportsPath); } catch { return []; } | |
| list(): ApexReportSummary[] { | |
| if (this.cachedSummaries) return this.cachedSummaries; | |
| if (!this.reportsPath) return []; | |
| let entries: string[]; | |
| try { entries = readdirSync(this.reportsPath); } catch { return []; } |
| return summaries.sort((a, b) => b.round - a.round || a.file.localeCompare(b.file)); | ||
| } |
There was a problem hiding this comment.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1777a6042a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .filter((summary) => summary.round === round) | ||
| .map((summary) => ({ | ||
| ...summary, | ||
| body: readFileSync(join(this.reportsPath, summary.file), 'utf8'), |
There was a problem hiding this comment.
Reject symlinked report files before reading them
When OPENMYTHOS_REPORTS_PATH points at a checkout or directory that can contain symlinks, a symlink named like APEX_R1_SECRET.md is accepted by the filename scan and this readFileSync follows it, so any authenticated read:evidence user can fetch the target file through /api/openmythos/apex/reports/1. Since the route exposes full bodies, please verify entries with lstat/realpath and only return regular files that resolve inside the configured reports directory.
Useful? React with 👍 / 👎.
Summary
The OpenMythos APEX research program (44 rounds: promotion gates, judge-reliability studies, warm-state controls, the R43 routed-ensemble result, the R44 selection negative-result) lives as markdown in a sibling repo, invisible to the platform. This makes it queryable.
ApexReportService— readsAPEX_R<round>_<slug>.mdfromOPENMYTHOS_REPORTS_PATHwith format-tolerant parsing across all three generations of report style: round from filename, title from first#heading, decision from an inlineDecision: `x`line or a## Decisionsection. Multiple reports per round supported (the R13/R14 reliability sets). Unset path → empty list; unreadable files skipped.GET /api/openmythos/apex/reports— index, newest round firstGET /api/openmythos/apex/reports/:round— all of a round's reports with bodies (round validated as a non-negative integer; files only ever come from the service's own directory scan — no user paths)Validation
negative_result, R43confirmed, R16 (section-style)promoted, R1 (no decision)nullDashboard surface deliberately deferred until #60 merges — both would touch the same
App.tsx/Layout.tsxregions.🤖 Generated with Claude Code