fix(correlation): unwrap seed envelopes in the correlation seeder's input reads - #6042
fix(correlation): unwrap seed envelopes in the correlation seeder's input reads#6042Yigtwxx wants to merge 1 commit into
Conversation
|
@Yigtwxx is attempting to deploy a commit to the World Monitor Team on Vercel. A member of the Team first needs to authorize it. |
…nput reads
fetchInputData bare-JSON.parse'd all nine INPUT_KEYS. Seven of them are
written by contract-mode seeders as { _seed, data }, so computeCorrelation's
field reads saw the envelope, resolved to undefined and fell through to [].
Only the two military:flights keys survived, because seed-military-flights.mjs
still writes bare — which left escalation, economic and disaster computing
over empty inputs.
It was silent: the hasAnyData tripwire tests `data[k] != null`, and an
envelope object is not null, so it never fired. The publish then failed its
card floor and runSeed resolved RETRY, holding the previous cards alive
without advancing _seed.fetchedAt.
Same defect, same fix and same reusable helper as koala73#5870 / koala73#5896 one seeder
over. Adds the per-key freshness gate that fix established, so unwrapping
cannot revive a preserved last-good envelope into cards stamped with a fresh
computedAt; every budget is the source seeder's own declared maxStaleMin.
_seed-envelope-source.mjs is already in the derived-signals bundle's
watchPatterns via _seed-utils.mjs, so no deploy manifest change is needed.
20273c0 to
cf6ead8
Compare
|
Closing and reopening only to re-trigger CI — no code change, head stays |
|
Context on the close/reopen above — it was a CI re-trigger, nothing changed. Head is still The first run after the rebase went red on One stablecoin request escaped before That can't originate here: this PR's diff is I don't have re-run rights on this repo, so close/reopen was the only way to re-trigger. The job passed on the second run against the identical SHA, and Worth flagging rather than burying, though: that assertion is a timing race that reproduced twice in a row on one loaded runner, so it can take an unrelated PR red again. For the record, the rebase itself: onto |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
I couldn't make changes to this PR because you maintainer edits disabled - it's now superseded by #6385, which includes this PR’s original commit and the follow-up hardening. Closing without merging. |
Summary
Follows #5896, whose "Out of scope" section named this file:
scripts/seed-correlation.mjsreads its inputs with the same envelope-blindJSON.parsethat #5870 was filed for, sits in the same Railway bundle, and registers the samemilitary:flights:{v1,stale:v1}pair. There is no issue open for it.scripts/seed-correlation.mjs:41-46:Seven of the nine
INPUT_KEYSare written by contract-mode seeders, which store{ _seed, data }(scripts/_seed-utils.mjs). So every field read incomputeCorrelation(:702-728) was reading the envelope:{_seed, data}.eventsisundefined,Array.isArray(envelope)isfalse, so each one fell through to[].military:flights:v1seed-military-flights.mjs(localredisSet, norunSeed)military:flights:stale:v1seed-military-flights.mjsunrest:events:v1seed-unrest-events.mjsprotests = []infra:outages:v1seed-internet-outages.mjsoutages = []seismology:earthquakes:v1seed-earthquakes.mjsearthquakes = []market:stocks-bootstrap:v1seed-market-quotes.mjsstockQuotes = []market:commodities-bootstrap:v1seed-commodity-quotes.mjscommodityQuotes = []market:crypto:v1seed-crypto-quotes.mjscryptoQuotes = []news:insights:v1seed-insights.mjsnewsClusters = []Mapped onto the four domains (
:761-783):runSeed.collectEscalationSignals(protests, outages, newsClusters), all three empty.collectEconomicSignals(allMarkets, newsClusters), all four empty.collectDisasterSignals(earthquakes, outages, protests), all three empty.Why nobody saw it. The
hasAnyDatatripwire at:699-700testsdata[k] != null, and an envelope object is not null, so "No input data available in Redis" never threw.validateFnthen failed theMIN_CORRELATION_CARDSfloor whenever military also produced nothing, andrunSeedresolvedcontractState = 'RETRY'(scripts/_seed-utils.mjs:2038-2040), which holds the previous cards alive without advancing_seed.fetchedAt. Exit 0, no alarm, stale cards — the same silent-degradation signature as #5870.The irony at
:704: the?? data['military:flights:v1']fallback exists to tolerate a raw-array shape. It would not have rescued an enveloped flights key either.The fix
The pattern #5896 established, unchanged:
unwrapEnvelopefromscripts/_seed-envelope-source.mjs. It only unwraps when_seed.fetchedAtis a number, so the bare flights keys pass through byte-identical, as does the legacy top-level-array shape theArray.isArrayfallbacks still handle.JSON.parsestays outsideunwrapEnvelope. It accepts a raw string, but on a parse failure returns that string asdata— which would register a malformed value as a found key and defeat thehasAnyDatatripwire. Same reasoning as the comment atseed-cross-source-signals.mjs:225-235.computedAt: Date.now(). Every budget is the source seeder's own declaredmaxStaleMinrather than a number chosen at this call site — and the five keys shared withseed-cross-source-signals.mjscome out at exactly the budgets its own table already uses, which is a useful cross-check. The twomilitary:flightskeys are absent from the table on purpose: written bare, they carry no_seedfor the gate to read.No deploy manifest change.
scripts/_seed-envelope-source.mjsis already listed inseed-bundle-derived-signals'swatchPatternsinscripts/railway-services.json, next toscripts/seed-correlation.mjsitself, and is pulled in transitively byscripts/_seed-utils.mjs:11which this seeder already imports.Audit of the remaining
scripts/readersSince the same defect had already appeared twice, I swept every
scripts/file that issues a RedisGETand parses the result, and checked each against its writer rather than trusting the key name:seed-correlation.mjsseed-hs2-chokepoint-exposure.mjs:210comtrade:*:v1fromseed-comtrade-bilateral-hs4.mjsSET(:558), notrunSeed, so the keys are bareseed-forecast-bets.mjs:285forecast:resolutions:v1, contract-modecollectOpenEnsembleIdsunwraps ad hoc withObject.values(ledger.data ?? ledger)(:230-232), and the feed reads handle_seedexplicitly infilterFreshFeeds/unwrapFeeds(:97-116)seed-portwatch-port-activity.mjs:913seed-wb-indicators.mjs:468-470seed-military-bases.mjs,seed-webcams.mjs,seed-resilience-static.mjs,seed-comtrade-bilateral-hs4.mjs,lib/brief-embedding.mjsseed-cross-source-signals.mjs,seed-regional-snapshots.mjs,regional-snapshot/_helpers.mjsSo
seed-correlation.mjswas the last envelope-blind cross-seeder reader inscripts/.seed-forecast-bets.mjsis worth a second look at some point — it is correct today, but its correctness lives in three separate consumers rather than at the read — and I have deliberately not touched it here.Design decisions for maintainer review
fetchInputDataandINPUT_KEYSare now exported so the test can drive the real reader instead of a copy.computeCorrelationis left unexported — the field reads are pinned throughfetchInputData's output, which is the seam the defect actually lives at.hasAnyDatatripwire is left as-is. With the unwrap in place it means what it says again. Making it stricter (say, requiring a minimum number of found keys) would be a behaviour change with its own alerting implications rather than a repair.Array.isArrayfallbacks incomputeCorrelationare left in place. They are dead for the keys that migrated, but they are what makes the read tolerant if any of these writers is ever rolled back, and removing them is not this PR's job. There is a test pinning that path.Verification
Every guard is mutation-proven:
JSON.parsefolded insideunwrapEnvelopeNo survivors.
Each of the seven enveloped fixtures asserts both directions: that the payload field survives the read, and that the pre-fix shape does not expose that field at all — so the bug stays pinned rather than merely fixed. Fixtures are driven through an
asReadByTheSeeder()helper built on the realunwrapEnvelope, so a test cannot assert against a shape the reader could not produce.INPUT_KEYSitself is pinned, so adding a tenth key forces a decision about its freshness budget.The existing
tests/seeder-validation-floors.test.mjsandtests/correlation-runtime-mode.test.mtsnever touched the read path — the first covers onlydeclareRecords/validateFn, the second greps the source text — which is why this was invisible.Other gates:
npm run test:data: identical failure set toorigin/main— 47 failing test names on both,commdiff empty in both directions.scripts/audit-railway-watch-paths.mjsneeds therailwayCLI, which I do not have locally, so I verified the bundle claim structurally instead:scripts/_seed-envelope-source.mjsis present in theseed-bundle-derived-signalswatchPatternsarray inscripts/railway-services.json.Out of scope
seed-forecast-bets.mjs's scattered unwrapping, described in the audit above. Correct today, structurally fragile, and its own change.seed-military-flights.mjsnot usingrunSeed. It is the reason the military domain still works, so migrating it is a change that should be made deliberately and with this reader already envelope-aware — which it now is.unwrapEnvelope, to any writer, or to the published card shape.Type of change
Affected areas
/api/*)scripts/seed-correlation.mjs(Railway derived-signals bundle)Checklist
[Correlation] inputs:line at:735reporting non-zeroprotests/outages/markets/news, and non-zerosignalson the escalation, economic and disaster lines.api/rss-proxy.jsallowlist (if adding feeds) — N/A, no feeds added.npm run typecheck)Documentation Alignment Checklist
N/A — no documentation claim is published or changed. The fix restores reads that were already specified; no methodology, API/MCP contract, generated doc or example changes. Listed for completeness:
correlation:cards-bootstrap:v1plus the four per-domain keys) keep their existing shape and TTL.