You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(api): forward the outbound-ping bindings the workflow env was dropping (#2200)
Follow-up to #2199. Two bugs, one root cause.
1. IndexNow has been a no-op on the workflow ingest path since it shipped.
`buildFetchOneEnv` is an exhaustive projection of the workflow env down to
`FetchOneEnv`, and it never forwarded `INDEXNOW_KEY`. Every workflow-driven
fetch therefore logged `indexnow / skipped / no_key_binding` and no search
engine was ever pinged. Confirmed in prod logs.
Every forwarded field is optional, so the omission type-checked. The
`GuardedFetchOneEnv` compile guard exists precisely to stop this -- it was
added after the same class of drop hit the Anthropic key, then again after
it hit the OpenRouter lane vars -- but its key list was named
`AiCriticalFetchKeys` and scoped to AI bindings, so a non-AI credential
fell straight through.
Renamed to `CriticalFetchKeys` and widened: the failure mode is not
"an AI pass is disabled", it is "an optional binding is dropped from an
exhaustive projection, the omission type-checks, and the feature degrades
to its disabled state in silence". Any binding whose absence is
indistinguishable from being deliberately switched off now belongs there.
`INDEXNOW_KEY`, `INDEXING_DISABLED`, `WEB_SERVICE_KEY` and `WEB_BASE_URL`
are now forwarded and guarded.
2. The #2199 revalidate ping missed the dominant ingest path.
`cron/poll-fetch.ts` runs its own post-insert effects block and never
calls `runBatchIngestEffects`, where the ping was wired -- so on-demand ISR
revalidation only fired for the manual fetch route and the scrape
persister, not for the workflow that drives most ingest. Wired into the
inline block beside the IndexNow call. It would also have skipped at
`no_secret_binding` regardless, for the reason above.
Both pings fail open to "skipped", which is why neither showed up as an
error -- the skip is indistinguishable from the feature being correctly
disabled. The extended `*-resolve-env` regression tests now pin all four.
Copy file name to clipboardExpand all lines: docs/architecture/web.md
+5-1Lines changed: 5 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -55,7 +55,11 @@ Dynamic routes carry `revalidate = 86400` so first-render cost amortizes across
55
55
56
56
## ISR revalidation
57
57
58
-
Org, source, product and release pages are statically rendered. **Freshness comes from an ingest-time ping, not from the clock.** When `runBatchIngestEffects` inserts releases it calls `notifyWebRevalidate` (`workers/api/src/lib/web-revalidate.ts`) — a sibling of the IndexNow ping, same trigger and same fire-and-forget semantics — which POSTs the affected `{ orgSlug, sourceSlug?, productSlug? }` to web's `POST /api/revalidate`. The route's whole contract (bearer auth, body validation, path derivation) lives in `web/src/lib/revalidate-request.ts` so it is testable without `next/cache`; the route module only injects `revalidatePath` and the secret.
58
+
Org, source, product and release pages are statically rendered. **Freshness comes from an ingest-time ping, not from the clock.**`notifyWebRevalidate` (`workers/api/src/lib/web-revalidate.ts`) — a sibling of the IndexNow ping, same trigger and same fire-and-forget semantics — POSTs the affected `{ orgSlug, sourceSlug?, productSlug? }` to web's `POST /api/revalidate`.
59
+
60
+
It is wired at **both** post-insert effect sites, which are separate code paths: `runBatchIngestEffects` (batch route + scrape persister) and the inline effects block in `cron/poll-fetch.ts` (the workflow path, which does not call `runBatchIngestEffects`). Wiring only the first leaves the ping firing for manual fetches alone — the workflow is the dominant ingest driver.
61
+
62
+
The credentials reach `fetchOne` through `buildFetchOneEnv` (`workers/api/src/workflows/_fetch-env.ts`), an exhaustive projection guarded by `CriticalFetchKeys`. **Any binding whose absence is indistinguishable from the feature being switched off must be listed there** — `INDEXNOW_KEY` was not, and the IndexNow ping was a silent no-op on the workflow path from the day it shipped. The route's whole contract (bearer auth, body validation, path derivation) lives in `web/src/lib/revalidate-request.ts` so it is testable without `next/cache`; the route module only injects `revalidatePath` and the secret.
59
63
60
64
`export const revalidate = 86400` on those pages is the **backstop for a dropped ping**, not the mechanism. Time-based ISR fits this content badly: the long tail of org pages is low-traffic enough that a short window regenerates pages nothing changed on (writes ≈ requests), while a long one would leave fresh releases invisible. Windows and the shared default live in `web/src/lib/isr.ts`.
0 commit comments