T-E3S6-e2e-server-detects-stale-binary
State: open/ready · Impact: medium · Complexity: small
AUTO-DEFINED: this spec was best-effort machine-authored by /sdlc:task-auto-define on 2026-09-27. Review the Goal, Approach, Today, Areas, and Acceptance-criteria carefully before trusting it.
A cargo build from a second worktree sharing the same target/ dir can leave
a stale platform-server binary that a later cargo run does not relink, so
the e2e suite silently runs against old server code instead of the current
worktree’s changes. Found while working
T-50QX-polish-rule-options-with-no-default on
sksizer/dev.
Every Playwright fixture server that exercises determined-platform’s real /api
boots the same way: solutions/determined/tests/ui/serve-smoke.ts,
serve-notes.ts, serve-vaults.ts, serve-fitness.ts, serve-polish.ts, and
serve-polish-lint.ts each spawn cargo run -p determined-platform --bin platform-server -- --port <N> --dist <built SPA> --vault <tmp> --state-dir <tmp> with stdio: 'inherit', and trust whatever cargo run hands back.
None of them independently confirms that the binary cargo starts
(target/debug/platform-server, linked from determined-platform and its
full local dependency closure) actually reflects the invoking worktree’s
current sources. That closure is bigger than solutions/determined/crates/*
(which holds most of it, including determined-polish, determined-fitness,
and determined-core): determined-platform’s own Cargo.toml also names
local path dependencies outside that directory — packages/rust/vault-fs,
packages/rust/wikilink, packages/rust/fleet, packages/rust/fleet-tauri,
packages/rust/dir-aspect, packages/rust/markdown-util,
packages/rust/yaml-splice, and packages/rust/atomic-write — and, deeper in
the graph, packages/rust/agent-adapt, packages/rust/easy-git,
packages/rust/fluent-git, packages/rust/prettier-fmt, packages/rust/slug,
packages/rust/vault-transform, and even
solutions/agent-pants/crates/agent-adapt-core (verified against the root
workspace Cargo.toml and cargo metadata’s resolved graph for the
determined-platform package). A staleness check scoped only to
solutions/determined/crates/* would miss an edit to any of those and
silently reproduce the exact bug this task exists to catch.
This repo does not itself configure a shared Cargo target directory: no
CARGO_TARGET_DIR or [build] target-dir override exists anywhere in the
tree, and target/debug/platform-server lives at each worktree’s own root by
default. But cargo run’s freshness check operates on whatever target
directory it is pointed at, without knowing which worktree’s absolute source
tree produced the cached fingerprints already there. A developer who points
CARGO_TARGET_DIR at one directory shared across several git worktrees of
this 86-crate Rust workspace — a common optimization, since a from-scratch
cargo build of the whole workspace is expensive to repeat per worktree —
can hit exactly what
T-50QX-polish-rule-options-with-no-default’s post-mortem reported: a
cargo build from a second worktree against that shared target left
platform-server unrelinked from the first worktree’s perspective. cargo run reported the binary fresh and executed it as-is, so the Playwright suite
booted, ran, and reported green against yesterday’s server binary while
solutions/determined/crates/determined-polish/src/lint/rule_options.rs (in
the first worktree) had already changed.
Proposed
Section titled “Proposed”Add one shared freshness check that every serve-*.ts script spawning
platform-server runs immediately before handing control to Playwright, so a
stale binary fails the e2e boot loudly — the process exits non-zero before the
port ever opens — instead of silently serving old server code for the whole
suite run. The check is independent of why the binary went stale (a shared
CARGO_TARGET_DIR across worktrees, a failed background build, clock skew);
it only asserts the observable property the Goal names: the binary on disk is
not older than the sources it is supposed to contain.
Approach
Section titled “Approach”- Add a new shared helper module,
solutions/determined/tests/ui/platform-server-freshness.ts, exporting a single function (e.g.assertPlatformServerFresh(determinedRoot: string): void) that:- Resolves the workspace root two levels above
determinedRoot(the same relative layout everyserve-*.tsscript already uses to compute its owndeterminedconstant) and the expected binary path,<workspaceRoot>/target/debug/platform-server(cargo run’s default, unreleased profile — none of these scripts pass--release). - If the binary does not exist yet, return immediately: a first-ever
cargo runfor this worktree is a cold build, not a staleness case. - Otherwise,
statSyncthe binary’smtimeMsand compare it against the newestmtimeMsamong every.rsfile, and everyCargo.toml, acrossdetermined-platform’s full local (path) dependency closure — not justsolutions/determined/crates/*/src/, which misses real local deps that live outside it (see Today) — plus the workspace’s rootCargo.lock. Derive that closure by runningcargo metadata --format-version 1once, through the repo’s command seam (spawnRunner/runCheckedinsolutions/ontological/lib/util/command.ts— this is a one-shot exec, not the live-streamingcargo runspawn the scripts already carry, so it does not qualify for that exception): walk theresolvegraph from thedetermined-platformnode over edges whosedep_kindsincludenull(normal) orbuild, and keep every reachable package whosesourceisnull(a workspace-local path dependency). Cache the resolved list for the process’s lifetime rather than re-runningcargo metadataperserve-*.tsinvocation, and walk each kept package’s directory (readdirSync(path, { recursive: true })— no new dependency needed). - When a source file’s
mtimeMsis newer than the binary’s, print a message naming the binary path, its mtime, and the newest offending source file, and callprocess.exit(1)before anycargo runorspawncall for the server itself runs.
- Resolves the workspace root two levels above
- Call
assertPlatformServerFresh(determined)at the top of each ofsolutions/determined/tests/ui/serve-smoke.ts,serve-notes.ts,serve-vaults.ts,serve-fitness.ts,serve-polish.ts, andserve-polish-lint.ts, right after each script computes its owndeterminedconstant and before it spawnscargo run -p determined-platform --bin platform-server. - Leave the existing
cargo runinvocation in every script untouched — it still makes the real build-or-skip decision and boots the server; the new check only adds a guard in front of trusting its output blindly. A genuinely fresh binary (cargo just relinked it, or nothing changed and the existing binary already postdates every source) passes the check with no behavior change. - Add
solutions/determined/tests/ui/platform-server-freshness.test.ts, a unit test over the mtime-comparison logic alone (temp directories/files with controlled mtimes viautimesSync), asserting: no binary → passes; a binary newer than every source → passes; a source file newer than the binary → reports the offending path and would exit non-zero. No realcargoinvocation needed for this test.
| Area | Note |
|---|---|
solutions/determined/tests/ui/platform-server-freshness.ts (new) | Shared mtime-based staleness check and its exported assertion function |
solutions/determined/tests/ui/serve-smoke.ts, serve-notes.ts, serve-vaults.ts, serve-fitness.ts, serve-polish.ts, serve-polish-lint.ts | Each calls the new check before its existing cargo run -p determined-platform --bin platform-server spawn |
solutions/determined/crates/determined-platform/src/bin/platform-server.rs | The binary the check protects; no change expected here |
determined-platform’s full local dependency closure, derived via cargo metadata (not a hardcoded glob) | The local crate source trees the check walks for mtimes — spans solutions/determined/crates/* (determined-polish, determined-fitness, determined-core, and others), packages/rust/* (e.g. vault-fs, wikilink, fleet), and solutions/agent-pants/crates/agent-adapt-core |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: A shared helper (
solutions/determined/tests/ui/platform-server-freshness.ts) exists that comparestarget/debug/platform-server’s mtime against the newest mtime among every.rsfile andCargo.tomlacrossdetermined-platform’s full local dependency closure — derived viacargo metadata, not a hardcodedsolutions/determined/crates/*glob; the closure also includespackages/rust/*crates such asvault-fs,wikilink, andfleet, plussolutions/agent-pants/crates/agent-adapt-core— and the rootCargo.lock. - AC-2: Each of
serve-smoke.ts,serve-notes.ts,serve-vaults.ts,serve-fitness.ts,serve-polish.ts, andserve-polish-lint.tscalls that helper before spawningcargo run -p determined-platform --bin platform-server. - AC-3: With a crate source file’s mtime forced newer than the on-disk
platform-serverbinary — simulating the shared-target-dir race fromT-50QX-polish-rule-options-with-no-default’s post-mortem — running any of the six scripts named in AC-2 exits non-zero, names the stale binary and the offending source file, and never opens the server’s port. - AC-4: With
platform-servergenuinely fresh (freshly linked, or already newer than every crate source), each of the six scripts named in AC-2 boots the server exactly as it does today — no added failure on the happy path. - AC-5: A unit test for the mtime-comparison logic
(
solutions/determined/tests/ui/platform-server-freshness.test.ts) passes without invoking realcargo.
Out of scope
Section titled “Out of scope”- none
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:spawn-task-pr on 2026-09-27 UTC from
T-50QX-polish-rule-options-with-no-default in
sksizer/dev.