Skip to content

ci(ingest): run each source in its own job and lift the timeout - #239

Open
amanthanvi wants to merge 5 commits into
mainfrom
cursor/ingest-per-source-jobs-956c
Open

amanthanvi wants to merge 5 commits into
mainfrom
cursor/ingest-per-source-jobs-956c

Conversation

@amanthanvi

@amanthanvi amanthanvi commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

What

Audit item E4. Restructures .github/workflows/ingest.yml so each source is ingested in its own job, under a timeout that fits the NIST crawl.

  • plan lists the ingestable sources with a new ingest --list [--source <slug>] mode. A mistyped or disabled workflow_dispatch slug now fails here, before anything is crawled.
  • regenerate is a matrix with one job per source (fail-fast: false, timeout-minutes: 90). Each job runs the adapter, then content:check, then uploads its own bundle-patch-<slug> artifact if its bundle changed.
  • combine runs after all source jobs, whether they passed or failed (!cancelled()). It applies the patches that passed, re-runs content:check on them together, and uploads the single content-patch artifact.
  • pull-request is unchanged, except for its gate and two new lines in the PR body: the sources included, and a link to the run. It is still the only job with write permissions.
  • A workflow-wide concurrency: ingest group serializes whole runs. Overlapping runs would otherwise crawl the same hosts twice, and an older run could publish after a newer one and roll ingest/auto-update back.

The crawler is untouched: robots.txt handling and the 250 ms per-host floor stay as they are.

Why

Since 2026-09-14, all four scheduled runs (most recently run 37275732452) were cancelled at the 30-minute timeout during the NIST crawl. The logs show exactly 100 pages every 25 s, which is the 250 ms floor. At that rate the 10,023 term pages need about 42 minutes. The floor arrived with v0.2.0 (#227) on 2026-09-12. Before that, the 2026-09-07 run crawled the same pages in about 12.5 minutes with 8 unpaced workers.

Why 90 minutes: NIST's maxItems cap is 15,000 pages, and 15,000 × 250 ms ≈ 63 minutes, plus index pages and setup. MITRE, NICCS, OWASP and RFC 4949 each take under a minute.

Parallel jobs stay polite because the crawler paces requests per host, within one process. The five sources use five different hosts, and a new test (gives each ingestable source its own host) fails if two enabled sources ever share one.

Heads-up: jobs will now fail later, at content:check (M1)

This PR gets the jobs past the timeout, but any source whose entries changed will still fail at content:check. The cause is the whole-corpus tag hash (audit M1). Today, in the live run below:

  • RFC 4949, NICCS, OWASP and NIST all fail at their own Validate content step. NIST alone reports 1,374 errors: the corpus-hash mismatch, tags pointing at entries the v2 adapter no longer emits, and stale tag examples.
  • MITRE passes. So once this merges, the next weekly run should open an ingest PR containing just the MITRE update (content/generated/mitre-attack-cti.json, 7 lines). Because of M2, CI won't run on that PR, since it is opened with GITHUB_TOKEN.

Out of scope, as agreed: making NIST resumable and switching the PR token to a GitHub App (M2), and the tag gate itself (M1).

How to test

  • pnpm gate: passes locally (Node 24), both before and after merging main. The ingest suite has a new sources.test.ts covering:
    • the selection rules
    • the checked-in registry
    • the distinct-host invariant
    • --list run as a subprocess, to prove stdout is pure JSON and that a bad or missing --source exits non-zero
  • actionlint 1.7.12 with ShellCheck 0.11.0: no findings in ingest.yml.
  • Local dry run of the workflow's own run: scripts, extracted with yq. It covers a changed bundle, a brand-new untracked bundle, an unchanged source, combine with zero artifacts, combine with two patches, and the pull-request job applying the combined patch.
  • Live run on GitHub Actions. A scratch branch (now deleted) held a push-triggered copy of this workflow with the PR step stubbed. It is semantically identical to the final file; only comments differ.
    • Full run 38086283107: NIST crawled all 10,023 pages at the polite pace in 41 m 46 s, and its job finished at 42 m 22 s, inside the 90-minute budget. The four failing sources failed independently. combine applied MITRE, re-validated it (content ok), and the stubbed PR job reported Would open a PR for: mitre-attack-cti.
    • Probe run 38086283139: one source job deliberately timed out (it ends as cancelled), one failed, and one produced a patch. combine and the PR job still ran with the good patch. This confirms that !cancelled() stays true when a source job times out.

Notes for the reviewer

  • Greptile raised two findings, both fixed: the --source flag with no slug, and per-job locks letting runs publish out of order.
  • Sourcery's "pending dispatch dropped" comment is declined, with the reasoning in its thread. GitHub keeps one pending run per concurrency group, so a third overlapping run replaces the pending one, which shows as cancelled and can be dispatched again.
  • CodeRabbit did not review. The repo is under its 10-star threshold, and it needs a human to tick "Trigger review" in its comment.

Checklist

  • Tests added or updated, or not needed
  • pnpm gate passes locally
  • Commit messages follow Conventional Commits (feat:, fix:, docs:, chore:, ...)
  • Docs updated (README.md, docs/**, content/README.md) if behavior changed
  • No secrets committed; .env* stays untracked and placeholders only
Open in Web Open in Cursor 

Summary by Sourcery

Restructure ingestion CI to run validated sources independently, tolerate partial failures, and publish successful changes together.

New Features:

  • Add source discovery and validation via ingest --list, including early rejection of invalid or disabled manual selections.
  • Run each ingestable source independently and combine successful validated changes into a single pull request.

Bug Fixes:

  • Prevent slow or failing sources from blocking other ingestion sources or causing scheduled crawls to time out.

Enhancements:

  • Serialize ingest workflow runs to avoid overlapping crawls and stale pull request updates.
  • Document the per-source ingestion and aggregation workflow.

CI:

  • Restructure the ingest workflow into planning, per-source matrix, combination, and pull-request stages with source-specific artifacts and a 90-minute crawl timeout.

Documentation:

  • Update the architecture overview to describe independent source jobs and combined pull requests.

Tests:

  • Add coverage for source selection, registry validation, distinct source hosts, and the JSON-only ingest --list CLI output.

The weekly Ingest workflow ran every source in one 30-minute job. Since the
polite crawler landed (250 ms per-host floor), the NIST crawl alone needs
about 42 minutes, so every run since 2026-09-14 was cancelled.

- plan job lists ingestable sources via a new `ingest --list` mode
- one regenerate job per source (fail-fast off, 90-minute timeout), each
  validating with content:check and uploading its own bundle diff
- combine job folds the passing diffs, re-checks them together, and hands
  one patch to the unchanged write-scoped pull request job
- concurrency group keeps two runs from crawling the same hosts at once

Co-authored-by: Aman Thanvi <amanthanvi@users.noreply.github.com>
@vercel

vercel Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated
synac-web Ready Ready Preview Oct 10, 2026 9:51pm UTC

Request Review

@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

@greptileai review

@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 1d03f416-d938-4c77-ad05-c5bc83e8a03f

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@sourcery-ai

sourcery-ai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR replaces the single timed ingest job with a concurrency-protected pipeline that validates and fans out source selection, crawls each source independently for up to 90 minutes, combines only successful validated patches, and opens one PR with source-level outcome visibility.

Sequence diagram for source planning and aggregated pull request

sequenceDiagram
    participant Workflow
    participant Plan
    participant SourceJobs
    participant Combine
    participant PullRequest

    Workflow->>Plan: ingest --list [--source slug]
    Plan-->>Workflow: JSON source slugs
    Workflow->>SourceJobs: Run one job per source
    par Each source job
        SourceJobs->>SourceJobs: ingest --source SOURCE
        SourceJobs->>SourceJobs: content:check
        SourceJobs-->>Combine: Upload bundle-patch-SOURCE
    end
    Combine->>Combine: Download bundle patches
    Combine->>Combine: git apply patches
    Combine->>Combine: content:check
    alt Validated changes exist
        Combine-->>PullRequest: Upload content-patch and sources
        PullRequest->>PullRequest: Open pull request
    else No validated changes
        Combine-->>PullRequest: No pull request
    end
Loading

File-Level Changes

Change Details Files
Restructure ingestion CI into validated planning, isolated parallel source jobs, aggregation, and PR creation.
  • Add a read-only planning job that emits a JSON source matrix and rejects invalid or disabled dispatch selections.
  • Run each ingestable source independently with a 90-minute timeout and non-fail-fast matrix behavior.
  • Upload per-source bundle patches only after source-level content validation succeeds.
  • Combine successful patches after all source jobs finish, validate the combined content, and publish one aggregate artifact.
  • Allow PR creation when some source jobs fail, while retaining write permissions only for the PR job.
.github/workflows/ingest.yml
Add source discovery and selection logic for safe workflow fan-out.
  • Load and schema-validate the checked-in source registry.
  • Define ingestability as enabled with an ingest adapter and return deterministic slug selections.
  • Support JSON-only --list output with optional source filtering and clear failures for invalid selections.
tools/ingest/src/sources.ts
tools/ingest/src/cli.ts
Add coverage for source selection, registry integrity, CLI behavior, and parallel-host safety.
  • Test enabled/disabled selection rules, unknown slugs, sorted output, and empty registries.
  • Verify all shipped ingestable sources have distinct hosts.
  • Exercise --list as a subprocess to ensure valid JSON stdout and non-zero invalid-input behavior.
tools/ingest/src/sources.test.ts
Document the new per-source ingestion and aggregation workflow.
  • Explain that slow or failed sources are isolated and successful validated changes are folded into one pull request.
docs/architecture/overview.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@greptile-apps

greptile-apps Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[High impact] The PR appears safe to merge; both previous findings are fixed and no new blocking issues were found.

Summary

The PR runs each source in a separate job, raises the crawl timeout to 90 minutes, and combines validated changes into one pull request.

  • Each source runs in its own 90-minute workflow job.
  • Passing source bundles join one checked pull request.
  • The ingest command lists eligible sources for the workflow.
  • Ingest workflow runs now take turns.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["Plan sources"] --> B["Regenerate each source"]
  B --> C["Check each source"]
  C --> D["Upload changed bundles"]
  D --> E["Combine and check patches"]
  E --> F["Open one pull request"]
  G["Workflow-wide ingest lock"] -.-> A
  G -.-> F
Loading

Reviews (4) · Last reviewed commit: "ci(ingest): serialize whole runs again" · Reviewed by Greptile

Comment thread tools/ingest/src/cli.ts
`ingest --list --source` with no value fell through to an unfiltered list.
Fail with a usage error instead, which also stops `--source --all` from
treating the next flag as a slug.

Co-authored-by: Aman Thanvi <amanthanvi@users.noreply.github.com>
@amanthanvi
amanthanvi marked this pull request as ready for review October 10, 2026 21:11
Copilot AI balanced review requested due to automatic review settings October 10, 2026 21:11

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

@coderabbitai review

@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

@greptileai review

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path=".github/workflows/ingest.yml" line_range="20-22" />
<code_context>

+# Overlapping runs would crawl the same hosts twice and race on the pull
+# request branch, so a second run waits for the first.
+concurrency:
+  group: ingest
+  cancel-in-progress: false
+
 jobs:
</code_context>
<issue_to_address>
**Pending dispatches are dropped**

When one ingest run is active, one is pending, and another scheduled or dispatched run arrives, the `ingest` concurrency group replaces its pending run with the newer run despite `cancel-in-progress: false`; the replaced dispatch never crawls its requested source, and there is no replay mechanism.

Use a queue or replay mechanism that preserves every scheduled and manual dispatch instead of relying on the concurrency group's single pending slot.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and a workflow or matrix/patch-combination defect could produce an incomplete or incorrect generated-content pull request, or leave runs queued behind the new concurrency group; those effects are bounded and can be corrected by closing the PR, rerunning ingestion, and reverting the workflow. Reverting restores the prior behavior, though already-created PRs, artifacts, and notifications require cleanup.

Blocking findings: .github/workflows/ingest.yml:22


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines +20 to +22
concurrency:
group: ingest
cancel-in-progress: false

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.

🟡 Medium · Pending dispatches are dropped

When one ingest run is active, one is pending, and another scheduled or dispatched run arrives, the ingest concurrency group replaces its pending run with the newer run despite cancel-in-progress: false; the replaced dispatch never crawls its requested source, and there is no replay mechanism.

Use a queue or replay mechanism that preserves every scheduled and manual dispatch instead of relying on the concurrency group's single pending slot.

Prompt for AI agents
In `.github/workflows/ingest.yml` at lines 20-22:

**Pending dispatches are dropped**

When one ingest run is active, one is pending, and another scheduled or dispatched run arrives, the `ingest` concurrency group replaces its pending run with the newer run despite `cancel-in-progress: false`; the replaced dispatch never crawls its requested source, and there is no replay mechanism.

Use a queue or replay mechanism that preserves every scheduled and manual dispatch instead of relying on the concurrency group's single pending slot.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Partly valid, so I changed the design in 0d1e2f0 instead of adding a queue. GitHub Actions has no native queue, and building a replay mechanism is out of scope for this item. The concurrency is now per job: regenerate queues on ingest-<source>, and pull-request queues on ingest-pull-request. A pending job can then only be replaced by a newer crawl of the same source, which does the same work with fresher data. Dispatches for different sources no longer block or drop each other. A replaced job also still shows as cancelled in the run list, so it isn't silent.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correction: the commit is 3c352d3.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Update: I reverted the per-job locks in 09432fe. Greptile pointed out that they let an older run publish after a newer one and roll the PR branch back, which is worse than this case. I'm declining a queue here: the workflow-wide group keeps runs ordered, and the only loss is a pending run replaced by a third overlapping one. That run still shows as cancelled and can be dispatched again. The trade-off is documented in the workflow comment, and a real queue or replay mechanism belongs with the ingest follow-ups (M2 or A2).

A workflow-wide concurrency group holds one pending run, so a third
overlapping run could silently drop a queued single-source dispatch. Queue
the regenerate jobs per source and the pull request job on its own group:
a pending job is then only ever replaced by a newer crawl of the same
source, and a dispatch for one source no longer waits on the NIST crawl.

Co-authored-by: Aman Thanvi <amanthanvi@users.noreply.github.com>
@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

@greptileai review

Comment thread .github/workflows/ingest.yml Outdated
Per-job locks let an older run publish after a newer one and roll the
pull request branch back to stale bundles. Restore the workflow-wide
group and document its cost: a third overlapping run replaces the
pending one, which shows as cancelled and can be dispatched again.

Co-authored-by: Aman Thanvi <amanthanvi@users.noreply.github.com>
@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

@greptileai review

…rce-jobs-956c

Co-authored-by: Aman Thanvi <amanthanvi@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — ab9b0c22 Deployed Oct 10, 2026 by vercel[bot]
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.

3 participants