Skip to content

fix(conflict): restore keyless GDELT fallback#5269

Merged
koala73 merged 4 commits into
mainfrom
codex/fix-gdelt-conflict-bulk-fallback
Jul 13, 2026
Merged

fix(conflict): restore keyless GDELT fallback#5269
koala73 merged 4 commits into
mainfrom
codex/fix-gdelt-conflict-bulk-fallback

Conversation

@koala73

@koala73 koala73 commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Without ACLED credentials, conflict seeding no longer collapses when GDELT DOC queries are throttled or return a misleading all-empty result. The seeder now falls back to GDELT's recent 15-minute event exports and produces the same priority-country conflict shape consumed by the escalation EMA.

The fallback range-reads the current manifest, downloads only the eight newest exports through the TLS storage origin, and bounds every network and decompression step. Manifest URLs, byte sizes, checksums, ZIP metadata, GDELT event classes, and priority-country mappings are validated before publication; if both DOC and bulk paths fail, the existing last-good preservation/retry contract remains fail-closed. The seeder lock was also resized and regression-pinned to cover the bulk leg after the parallel auxiliary feeds settle.

Related: #5256

Validation

  • Live GDELT probe: export 20260713113000, 8/8 exports succeeded, 94 material-conflict records across 9 priority countries.
  • npm run build:full completed successfully.
  • Final focused suite: 64/64 passed, including DOC throttling, empty-DOC recovery, manifest trust, checksum, compression, streamed-size, partial-export, and runtime-budget cases.
  • Canonical data suite: 15,098 passed and 6 skipped; its two built-output prerequisite failures passed 9/9 after the required full build.
  • Pre-push gate passed typechecks, boundary/safe-HTML/rate-limit/premium-fetch checks, edge bundling, changed tests, version sync, and repository safety guards.

Post-deploy monitoring

  • Confirm the first no-ACLED seed logs GDELT bulk conflict-events fallback with a non-zero record count.
  • Verify acled-intel seed metadata stays current and non-zero for two 30-minute intervals.
  • Alert on bulk fallback failed or RETRY FAILED; if repeated, inspect storage-origin reachability and manifest/schema drift before rerunning the seeder.

Compound Engineering
GPT-5

@strix-security

strix-security Bot commented Jul 13, 2026

Copy link
Copy Markdown

Strix Security Review

No security issues found.

Updated for 8a76d18.


Reviewed by Strix
Re-run review · Configure security review settings

@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
worldmonitor Ignored Ignored Preview Jul 13, 2026 2:47pm

Request Review

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR restores a keyless GDELT conflict signal by adding a new _conflict-gdelt-bulk.mjs module that falls back to GDELT's official 15-minute bulk event exports when the DOC 2.0 API is throttled or returns a misleadingly empty result. The seeder lock TTL is raised from 360 s to 420 s to accommodate the additional worst-case bulk fetch window, and the timing invariant test is updated to match.

  • New bulk fallback module (_conflict-gdelt-bulk.mjs): fetches the manifest tail via a Range request, validates URL/size/MD5/ZIP metadata, decompresses with a 30 MB output cap, filters rows to material-conflict CAMEO codes and priority FIPS country codes, and deduplicates events across eight concurrent export downloads.
  • Seeder integration (seed-conflict-intel.mjs): triggers the bulk path when successfulCountries < floor || events.length === 0; fail-closed if both legs fail; lock TTL and deadline arithmetic updated with a regression-pinning test.
  • Test coverage: 64 new/updated cases across three test files cover DOC throttling, empty-DOC recovery, manifest trust, checksum, compression, streamed-size, partial-export, and runtime-budget invariants.

Confidence Score: 4/5

The change is fail-closed: if both DOC and bulk paths fail the existing last-good/retry contract is preserved, timing math is regression-pinned, and the invariant holds.

The bulk fallback module is solidly implemented with URL allowlisting, per-byte size caps, MD5 integrity, ZIP flag checks, and decompression output limits. The pagination object reports countriesSucceeded: CONFLICT_COUNTRIES.length even on partial DOC failure, masking degradation from monitoring consumers that track that field. A second edge: no HTTP 206 assertion on the Range-based manifest fetch means a CDN ignoring the Range header could silently supply oldest-manifest data instead of newest.

The bulk fallback integration in scripts/seed-conflict-intel.mjs (pagination metadata) and the Range request handling in scripts/_conflict-gdelt-bulk.mjs deserve a second look.

Important Files Changed

Filename Overview
scripts/_conflict-gdelt-bulk.mjs New module implementing the GDELT 15-minute bulk export fallback; URL allowlist, size bounds, MD5 checksum, ZIP parsing, and decompression limits are all present. One edge: no 206 status check on the Range-based manifest fetch means a 200 response without content-length would silently supply oldest-manifest data instead of newest.
scripts/seed-conflict-intel.mjs Adds bulk fallback path triggered by successfulCountries < floor
tests/conflict-gdelt-bulk.test.mjs New test suite covering manifest parsing, URL allowlisting, checksum mismatch, partial export failure, streamed size bounding, stored and deflated ZIP extraction, and event mapping.
tests/conflict-gdelt.test.mjs Updated to inject a fetchBulkEvents stub in all existing tests that exercise the coverage-floor error path, and adds two new tests for zero-event DOC sweep and throttled DOC recovery cases.
tests/seed-fetch-deadline-budget-invariants.test.mjs Adds GDELT_BULK_WORST_NETWORK_MS to the worst-case timing formula; the invariant correctly asserts that 396 s fits within the 420 s lock TTL.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Seeder as seed-conflict-intel
    participant DOC as GDELT DOC 2.0
    participant Bulk as _conflict-gdelt-bulk
    participant GCS as GCS storage.googleapis.com

    Seeder->>DOC: fetchGdeltCountryEvents x20 countries batched
    alt DOC sweep OK AND events not empty
        DOC-->>Seeder: events array
        Seeder->>Seeder: return events source undefined
    else DOC below floor OR zero events
        DOC-->>Seeder: throttled or empty
        Seeder->>Bulk: fetchBulkEvents
        Bulk->>GCS: "GET masterfilelist.txt Range bytes=-16384"
        GCS-->>Bulk: 206 manifest tail
        Bulk->>Bulk: parseGdeltRecentExports 8 descriptors
        loop 8 exports concurrency 4
            Bulk->>GCS: GET export.CSV.zip
            GCS-->>Bulk: ZIP bytes
            Bulk->>Bulk: verify size MD5
            Bulk->>Bulk: extractGdeltExportCsv inflateRawSync
            Bulk->>Bulk: mapGdeltExportToConflictEvents
        end
        alt at least one export succeeded
            Bulk-->>Seeder: events exportTimestamp
            Seeder->>Seeder: return source gdelt-bulk
        else all exports failed
            Bulk-->>Seeder: throw all exports failed
            Seeder->>Seeder: throw combined error
        end
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Seeder as seed-conflict-intel
    participant DOC as GDELT DOC 2.0
    participant Bulk as _conflict-gdelt-bulk
    participant GCS as GCS storage.googleapis.com

    Seeder->>DOC: fetchGdeltCountryEvents x20 countries batched
    alt DOC sweep OK AND events not empty
        DOC-->>Seeder: events array
        Seeder->>Seeder: return events source undefined
    else DOC below floor OR zero events
        DOC-->>Seeder: throttled or empty
        Seeder->>Bulk: fetchBulkEvents
        Bulk->>GCS: "GET masterfilelist.txt Range bytes=-16384"
        GCS-->>Bulk: 206 manifest tail
        Bulk->>Bulk: parseGdeltRecentExports 8 descriptors
        loop 8 exports concurrency 4
            Bulk->>GCS: GET export.CSV.zip
            GCS-->>Bulk: ZIP bytes
            Bulk->>Bulk: verify size MD5
            Bulk->>Bulk: extractGdeltExportCsv inflateRawSync
            Bulk->>Bulk: mapGdeltExportToConflictEvents
        end
        alt at least one export succeeded
            Bulk-->>Seeder: events exportTimestamp
            Seeder->>Seeder: return source gdelt-bulk
        else all exports failed
            Bulk-->>Seeder: throw all exports failed
            Seeder->>Seeder: throw combined error
        end
    end
Loading

Comments Outside Diff (1)

  1. scripts/seed-conflict-intel.mjs, line 333-346 (link)

    P2 Bulk fallback overstates DOC country health in pagination

    When the fallback fires because the DOC sweep yielded zero events but met the successfulCountries floor, the returned pagination sets countriesSucceeded: CONFLICT_COUNTRIES.length and countriesFailed: 0. A consumer that monitors the countriesSucceeded / countriesFailed pair to detect partial DOC degradation would see a "fully healthy" DOC sweep even though a chunk of country fetches may have silently returned empty results. The source: 'gdelt-bulk' field is the only signal that something went wrong upstream, and it requires callers to branch on a field that previously didn't exist.

Reviews (1): Last reviewed commit: "fix(conflict): restore keyless GDELT fal..." | Re-trigger Greptile

Comment thread scripts/_conflict-gdelt-bulk.mjs
@koala73

koala73 commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

Bulk fallback overstates DOC country health in pagination

Fixed in 86e906ba7. Bulk recovery now preserves the DOC sweep's actual countriesSucceeded and countriesFailed values while retaining the separate bulk export telemetry. Added coverage for a 16-success/4-failure DOC sweep recovered by bulk, and corrected the all-throttled expectation to 0/20.

@koala73 koala73 merged commit 2ac58a0 into main Jul 13, 2026
26 checks passed
@koala73 koala73 deleted the codex/fix-gdelt-conflict-bulk-fallback branch July 13, 2026 15:00
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