Skip to content

feat(showcase): run HTTP-only probe families in-process on the fleet control-plane#5284

Merged
jpr5 merged 4 commits into
mainfrom
feat/http-inprocess-control-plane
Jun 5, 2026
Merged

feat(showcase): run HTTP-only probe families in-process on the fleet control-plane#5284
jpr5 merged 4 commits into
mainfrom
feat/http-inprocess-control-plane

Conversation

@jpr5

@jpr5 jpr5 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Make the fleet control-plane ALSO run the 8 HTTP-only probe families in-process (they don't need the BrowserPool/worker), by lifting the legacy boot() probe-loader machinery into runControlPlane. Before this, the control-plane only ran the d6 producer and the 8 HTTP families were dark on the fleet.

HTTP-only families now run in-process: smoke, starter_smoke, image_drift, qa, aimock_wiring, version_drift, pin_drift, redirect_decommission. The browser families (e2e_d6 / e2e_smoke / e2e_demos / e2e_deep) are deliberately NOT run in-process — d6 goes via the worker producer path; the rest need a BrowserPool the control-plane does not own. This is independent of the worker-registry / worker-loop work.

Design

  • BROWSER_KINDS (exported) = {e2e_d6, e2e_smoke, e2e_demos, e2e_deep}. HTTP = every kind NOT in this set. Single source of truth for the partition.
  • registerHttpProbeDrivers (exported) registers only the 8 HTTP drivers (no BrowserPool drivers) — kept separate from registerAllProbeDrivers so the control-plane's probeRegistry is HTTP-only.
  • In runControlPlane: build an HTTP-only probeRegistry + a discovery registry wiring the SAME sources boot() uses (railway-services cached 24h with an auth tracker, pnpm-packages), a createProbeLoader scoped to HTTP kinds, and the same diffProbeSchedules/buildProbeInvoker loop boot() runs — registering one probe:<id> scheduler entry per YAML config on the control-plane's scheduler. Crons are driven FROM the YAML (cfg.schedule), never hardcoded. Each tick flows through the same statusWriter pipeline as the worker-result aggregator. Hot-reload via probeLoader.watch mirrors boot().
  • includeKind predicate added to createProbeLoader: a browser YAML on disk is SKIPPED (not rejected) against the HTTP-only registry, so a present e2e_* YAML never surfaces a spurious probes.reload.failed.

/health handling

The control-plane now owns in-process probe rules, so ruleCount reflects the real in-process HTTP probe count (was a hardcoded 0). The role: "control-plane" rules>0 gate-drop is retained (liveness is governed by the scheduler signals already folded into loopOk), but the real count means a silent probe-loader failure (zero HTTP probes loaded) is now VISIBLE on /health rather than masked behind the role short-circuit. schedulerJobCount already counts the new probe: entries (covers BOTH the producer entry and the probe entries).

Teardown: the HTTP-probe file watcher is torn down on stop() and on bind failure.

Red→Green evidence

Wrote failing tests first, confirmed RED against baseline (stashed the impl):

  • scheduling test: probe:smoke / probe:image_drift not registered (FAIL)
  • /health test: rules < 2 (FAIL)
  • BROWSER_KINDS is not iterable (FAIL)
  • loader includeKind skip behavior

After implementing, all GREEN.

Test plan

  • New runControlPlane in-process HTTP probe tests (scheduling partition + /health rule count + BROWSER_KINDS) — green, RED-verified
  • New createProbeLoader includeKind skip-not-reject test — green
  • Full harness vitest suite: 119 files / 2076 tests passed
  • Production build tsc -p tsconfig.build.json: clean (the only --noEmit error is the pre-existing toReversed lib-target issue in an unrelated test file, excluded from the build config)

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jun 5, 2026 10:52pm
research-canvas Ready Ready Preview, Comment Jun 5, 2026 10:52pm
travel Ready Ready Preview, Comment Jun 5, 2026 10:52pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
chat-with-your-data Skipped Skipped Jun 5, 2026 10:52pm
form-filling Skipped Skipped Jun 5, 2026 10:52pm

Request Review

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

📣 Social Copy Generator

Generate social media copies (Twitter/X, LinkedIn, Blog Post) for this PR using Claude.

  • Generate social media copies

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Production Digest-Pinning Audit

Audit could not run this round.

Error
graphql error: GraphQL: ServiceInstance not found

Run 2026-06-05 15:49:10 PDT — 0 finding(s).

@vercel
vercel Bot temporarily deployed to Preview – form-filling June 5, 2026 21:47 Inactive
@vercel
vercel Bot temporarily deployed to Preview – chat-with-your-data June 5, 2026 21:47 Inactive
jpr5 added a commit that referenced this pull request Jun 5, 2026
…ucer schedules (#5285)

## Summary

Producer-side foundation for fleet framework item 3b. Two
**behavior-preserving** generalizations that make the seam capable of
multiple browser families and multiple producer cadences, while keeping
the d6 case **byte-identical**. No wiring is flipped on yet (see Out of
Scope).

### 1. Parameterized service enumerator
`showcase/harness/src/fleet/control-plane/catalog-enumerator.ts`
- New generic `createServiceEnumerator(params)`
(catalog-enumerator.ts:215) takes the service-set `filter`, the
`driverKind`, and a `probeKeyPrefix` (string prefix → `<prefix>:<slug>`,
or a builder fn).
- `createD6ServiceEnumerator` (catalog-enumerator.ts:280) is
re-expressed as a thin call passing the d6 params: `D6_DRIVER_KIND`
(`e2e_d6`), prefix `"d6"` (→ `d6:<slug>`), and `D6_DISCOVERY_FILTER`. d6
output is unchanged — same services, same filter, same kind, same keys.

### 2. Control-plane accepts an array of producer schedules
`showcase/harness/src/fleet/control-plane/control-plane.ts`
- New `ProducerSchedule` type (`{ scheduleId, cron, producer }`) + a
`schedules?` dep on `ControlPlaneDeps`.
- `createControlPlane` normalizes to an array (control-plane.ts:~232);
omitting `schedules` degenerates to the single d6 schedule on
`FLEET_PRODUCER_SCHEDULE_ID` (`fleet-job-producer`) @ `40 * * * *` —
current behavior preserved exactly.
- `start()` / `stop()` iterate the array, registering/unregistering each
scheduler entry and starting/stopping each producer.

## Out of scope (deferred — gated on other in-flight PRs)
- **No `runControlPlane` wiring** to actually PASS multiple schedules —
that edit conflicts with in-flight **#5284** (which edits
`runControlPlane`) and is deferred. This PR only makes
`control-plane.ts` *capable* of N schedules + generalizes the enumerator
seam; the wiring lands later.
- No `e2e_smoke` / `e2e_demos` / `e2e_deep` enumerators or producers
(Phase 2).
- No changes to `worker-loop.ts` / `payload-mapper.ts` /
`probe-loader.ts` (other PRs own those).
- No driverKind constant / contract changes.

## Test plan
- [x] Red→green TDD: 3 new enumerator tests (generic
kind/keys/filter/fn-prefix) + 2 new control-plane tests (N entries
registered with distinct crons; stop tears all down) failed before impl,
pass after.
- [x] Equivalence: all pre-existing d6-enumerator + single-schedule
control-plane tests pass unchanged.
- [x] Full harness suite green: **2078 passed** (119 files).
- [x] `tsc -p tsconfig.build.json` clean (exit 0).
- [x] Only the 4 intended files changed; no lockfile drift.

Do not merge — producer-side foundation only; wiring follows after #5284
lands.
jpr5 and others added 3 commits June 5, 2026 15:46
…control-plane

The fleet control-plane previously ran only the d6 producer, leaving the 8
HTTP-only probe families (smoke, starter_smoke, image_drift, qa, aimock_wiring,
version_drift, pin_drift, redirect_decommission) dark on the fleet. Lift the
legacy boot() probe-loader machinery into runControlPlane so those families run
in-process:

- Add BROWSER_KINDS = {e2e_d6, e2e_smoke, e2e_demos, e2e_deep}; HTTP = every
  other kind. Add registerHttpProbeDrivers (HTTP-only driver set, no BrowserPool
  drivers).
- In runControlPlane, build an HTTP-only probeRegistry + discovery registry
  (railway-services cached + pnpm-packages, mirroring boot()), a probe-loader
  scoped to HTTP kinds, and the same diffProbeSchedules/buildProbeInvoker loop
  boot() uses — registering one probe:<id> scheduler entry per YAML config.
  Crons are driven from the YAML schedule. Browser e2e_* YAMLs route to the
  worker producer path and are NOT scheduled in-process.
- Add an includeKind predicate to createProbeLoader so a browser YAML on disk is
  SKIPPED (not rejected) against the HTTP-only registry — no spurious
  probes.reload.failed.
- /health: ruleCount now reflects the in-process HTTP probe count (was a
  hardcoded 0). The control-plane role still drops the rules>0 gate, but the
  real count means a silent probe-loader failure is visible on /health rather
  than masked. schedulerJobCount already counts the new probe entries.
- Tear down the HTTP-probe file watcher on stop() and on bind failure.
…d CR gap-fills

Address the CR gaps on the in-process HTTP-probe control-plane:

- Sweep orphaned `running` probe_runs at control-plane boot (boot()'s
  sweepStaleRuns never ran in fleet mode → leaked rows forever). Best-effort;
  a sweep failure does not abort boot.
- Add a fail-loud BROWSER_KINDS / HTTP-driver disjointness assert at boot plus
  a drift-lock test mirroring registerAllProbeDrivers, so a mis-added kind
  can't silently go dark.
- Assert the PR's headline guarantees that were unasserted: cron-from-YAML
  (exact values), /health loader-failure boot-survival + probes.reload.failed
  emit + rules=0 observability, hot-reload add/remove + watcher teardown, a
  discovery-backed family (qa) in the in-process schedule, and tightened
  /health rule/job counts to exact equality.
- Bump the includeKind skip log to info; make diffHttpProbeSchedules'
  unregister-failure post-state explicit (keep config observable); correct the
  /health "no longer masks" comment, the discovery-source comment
  (image_drift uses railway-services; version_drift uses pnpm-packages), the
  reload-failed surface comment (no bus subscriber on the control-plane), and
  drop transitional-rot tags.
@jpr5
jpr5 force-pushed the feat/http-inprocess-control-plane branch from 30c6d2f to a3ec4f5 Compare June 5, 2026 22:48
@vercel
vercel Bot temporarily deployed to Preview – form-filling June 5, 2026 22:48 Inactive
@vercel
vercel Bot temporarily deployed to Preview – chat-with-your-data June 5, 2026 22:48 Inactive
@vercel
vercel Bot temporarily deployed to Preview – chat-with-your-data June 5, 2026 22:49 Inactive
@vercel
vercel Bot temporarily deployed to Preview – form-filling June 5, 2026 22:49 Inactive
@jpr5
jpr5 merged commit 54de90d into main Jun 5, 2026
3 of 6 checks passed
@jpr5
jpr5 deleted the feat/http-inprocess-control-plane branch June 5, 2026 22:51
jpr5 added a commit that referenced this pull request Jun 5, 2026
…the fleet (#5287)

## Summary

Phase 2 of the harness pool-fleet migration: make the three remaining
BROWSER probe families (`e2e_smoke`, `e2e_demos`, `e2e_deep`) actually
run on the fleet by wiring producers into the control-plane. The worker
`DriverRegistry` for all four browser kinds already landed in #5283;
this PR is purely the PRODUCER side. Builds on #5283/#5284/#5285/#5286
(all merged).

### Unit A — three catalog-enumerator factories
`createE2eSmokeServiceEnumerator` / `createE2eDemosServiceEnumerator` /
`createE2eDeepServiceEnumerator`, each a thin specialization of the
generic `createServiceEnumerator` (the #5285 seam) with its own
`driverKind` + dashboard `probeKey` prefix and the shared
`D6_DISCOVERY_FILTER`:

| family | driverKind | probeKey prefix | verified against |
|---|---|---|---|
| smoke | `e2e_smoke` | `d4:<slug>` | `src/cli/targets.ts` `d4:${slug}`
|
| demos | `e2e_demos` | `e2e-demos:<slug>` |
`config/probes/e2e-demos.yml` `id: e2e-demos` |
| deep | `e2e_deep` | `d5-single-pill-e2e:<slug>` | `src/cli/targets.ts`
`d5-single-pill-e2e:${slug}` |

### Unit B — multi-schedule wiring in `runControlPlane`
`runControlPlane` now builds four producers and passes a `schedules`
array to `createControlPlane` (the #5285 multi-schedule API) via a new
pure `buildProducerSchedules` helper. Crons are read **literally from
the config YAMLs** — the deliberate offsets stagger the four families'
Playwright fan-outs on the shared `BrowserPool`:

| scheduleId | cron | source |
|---|---|---|
| `fleet-job-producer` | `40 * * * *` | d6 (unchanged; still honors
`FLEET_PRODUCER_CRON`) |
| `fleet-producer-e2e-smoke` | `*/15 * * * *` | `e2e-smoke.yml` |
| `fleet-producer-e2e-demos` | `10 * * * *` | `e2e-demos.yml` |
| `fleet-producer-e2e-deep` | `5,20,35,50 * * * *` | `e2e-deep.yml` |

The in-process HTTP probe runner (#5284) and the d6 producer's REQ-B
sweep leg are left intact (additive). The worker registry is **not**
touched.

### R-timeout (demos) — addressed
The demos driver's 20-min outer cap is threaded in-process via the
legacy `E2E_DEMOS_TIMEOUT_MS` env, which the **fleet worker never sets**
— so without a fix the 38-demo service would blow the driver's 5-min
`DEFAULT_TIMEOUT_MS` and go all-red. Fix:
- New `E2E_DEMOS_TIMEOUT_MS` SSOT const (mirrors `e2e-demos.yml`
`timeout_ms`) in the enumerator module.
- `createE2eDemosServiceEnumerator` conveys the cap per-job in
`driverInputs.timeout_ms` (smoke/deep convey nothing — verified no
`timeout_ms` on their specs, and d6's spec shape is unchanged).
- The demos driver now reads `input.timeout_ms` as a resolution source:
`ctx.env.E2E_DEMOS_TIMEOUT_MS` (legacy) > `input.timeout_ms` (fleet) >
`deps.timeoutMs` > `DEFAULT_TIMEOUT_MS`. Schema gains `timeout_ms:
z.number().int().positive().optional()`.

### Risks honored
- **R3 pool-contention (deploy-time):** all four families' jobs are
claimed by the same pooled worker(s) drawing from ONE `BrowserPool`
under `BROWSER_POOL_MAX_CONTEXTS` (24). Per-family `max_concurrency`
governs producer ENQUEUE width, not worker execution. The guard is the
offset crons + the 24-cap — preserved faithfully here. **Verify at
deploy time by triggering each family's producer and confirming the
BrowserPool does not starve.**
- **R1 context-headers (verified, no change):** both
`createPooledE2eSmokeLauncher` and `createPooledE2eDeepLauncher` thread
`contextOpts.extraHTTPHeaders`, so smoke + deep set their per-slug
`X-AIMock-Context` themselves.

## Test plan
- [x] Unit A: `createE2eSmoke/Demos/Deep` factories stamp the right
driverKind + probeKey shape and carry driverInputs; demos conveys
`timeout_ms` (default + override); d6 equivalence (no `timeout_ms`)
asserted — red→green verified.
- [x] Demos driver reads `input.timeout_ms` when the env is absent
(fleet path), env still wins over input (precedence) — red→green
verified.
- [x] Unit B: `buildProducerSchedules` emits 4 schedules with exact ids
+ crons; `FLEET_PRODUCER_CRON` override applies to d6 only;
`runControlPlane` registers all 4 producer schedules on the live
scheduler alongside the `probe:*` HTTP entries — red→green verified.
- [x] Full harness suite green: **120 files / 2128 tests passed**.
- [x] `tsc --noEmit` clean except the lone pre-existing `toReversed`
error.
- [ ] Deploy-time: trigger each family's producer and verify BrowserPool
does not starve under co-firing (R3).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
pull Bot pushed a commit to TheRakeshPurohit/CopilotKit that referenced this pull request Jun 5, 2026
…schedule producers

Producer-side foundation for fleet item 3b — two behavior-preserving
generalizations, byte-identical for the d6 case:

1. Generalize the d6 service enumerator into a parameterized
   `createServiceEnumerator(params)` carrying the service-set filter, the
   driverKind, and the probeKey prefix builder. `createD6ServiceEnumerator`
   is now a thin wrapper passing the d6 params (e2e_d6 kind, d6:<slug> keys,
   D6_DISCOVERY_FILTER), so d6 behavior is unchanged.

2. Generalize createControlPlane to accept an array of
   { scheduleId, cron, producer } entries and register each on the scheduler.
   The single-d6 case degenerates to a one-element array on
   fleet-job-producer @ 40 * * * *, preserving current behavior.

Out of scope (gated on in-flight PRs): orchestrator runControlPlane wiring
to pass multiple schedules (conflicts with CopilotKit#5284), the e2e_smoke/demos/deep
families (Phase 2), and worker-loop/payload-mapper/probe-loader.
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