Skip to content

feat: APEX report ingester — research provenance queryable in-product#67

Open
djimit wants to merge 1 commit into
mainfrom
feat/apex-report-ingester
Open

feat: APEX report ingester — research provenance queryable in-product#67
djimit wants to merge 1 commit into
mainfrom
feat/apex-report-ingester

Conversation

@djimit

@djimit djimit commented Jul 16, 2026

Copy link
Copy Markdown
Owner

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 — reads APEX_R<round>_<slug>.md from OPENMYTHOS_REPORTS_PATH with format-tolerant parsing across all three generations of report style: round from filename, title from first # heading, decision from an inline Decision: `x` line or a ## Decision section. 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 first
  • GET /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

  • 3 unit tests over a fixture corpus covering all three format generations, multi-report rounds, non-APEX file exclusion, and missing-path behavior
  • Live against the real corpus: 73 reports across 43 rounds parsed; spot-checked decisions: R44 negative_result, R43 confirmed, R16 (section-style) promoted, R1 (no decision) null
  • Full server suite 1,216 green; type-check + lint clean

Dashboard surface deliberately deferred until #60 merges — both would touch the same App.tsx/Layout.tsx regions.

🤖 Generated with Claude Code

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-bot

kilo-code-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

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.

@gemini-code-assist gemini-code-assist 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.

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.

Comment on lines +31 to +32
export class ApexReportService {
constructor(private reportsPath: string = process.env.OPENMYTHOS_REPORTS_PATH || '') {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

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_-]+)`?/);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

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.

Suggested change
const word = next.match(/`?([A-Za-z_-]+)`?/);
const word = next.match(/^(?:Decision:\\s*)?(?:[-*\\s]+)?\\x60?([A-Za-z_-]+)\\x60?[.\\s]*$/);

Comment on lines +61 to +64
list(): ApexReportSummary[] {
if (!this.reportsPath) return [];
let entries: string[];
try { entries = readdirSync(this.reportsPath); } catch { return []; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Update the list() method to return the cached summaries if they have already been loaded, and populate the cache on the first run.

Suggested change
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 []; }

Comment on lines +83 to +84
return summaries.sort((a, b) => b.round - a.round || a.file.localeCompare(b.file));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Store the sorted summaries in the cache before returning them.

Suggested change
return summaries.sort((a, b) => b.round - a.round || a.file.localeCompare(b.file));
}
this.cachedSummaries = summaries.sort((a, b) => b.round - a.round || a.file.localeCompare(b.file));
return this.cachedSummaries;
}

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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'),

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 Badge 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 👍 / 👎.

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.

1 participant