docs: record Vikunja 502 focused verification and rebase evidence

This commit is contained in:
rocko committed 2026-10-22 00:06:35 -05:00
1 parent 8e3c6a79bf
commit 9bf4683c65
38 files changed
+1526

No files matched your search

+128
View File
@@ -0,0 +1,128 @@
# Vikunja502: processing test and runner hardening
Briefed by Rocko before implementation, from sage-2460 and mos-1267/1268.
Parent row191 is landed at0fa7bf421. This follow-up changes tests and the
owned runner copy; it does not change the live stack or processing policy.
Queue next currently offers211; Sage's explicit502 assignment governs this
work. No211claim or unrelated row work.
## Outcome and bounds
Keep row191's product behavior unchanged while making its failure evidence
precise and bounded. Four accepted fixes only:
1. packages/processing-control/tests/stop-run.test.mjs:106-108: the second
host must pair its real sleep with a real advancing clock. Override now
with Date.now alongside its existing real sleep. Retain all identity,
signal, ownership and receipt assertions. Replaying SR08 must produce a
named assertion failure and exit rather than hang until the harness kills it.
2. packages/webui/tests/processing.test.mjs:52: replace coarse status>=400
with a case table of exact status/error outcomes and no writer call per
refusal. Keep valid same-origin requests as the baseline; isolate the
unnamed-host case from the unrelated outer-origin refusal. The existing
direct-handler origin/site test stays intact. Replay KS43 and KS44 against
their appropriate existing tests to prove the right guard is detected.
3. packages/bus/tests/tasks.test.mjs:252-265: close the opened runtime in
finally around the dependency assertions. Keep each assertion and the
subsequent close-count check. Replaying KS54 must yield its named
dependency assertion and terminate without a90second hang.
4. Own-dir run-mutants.py: generic timeout causes must reflect the actual
same-invocation control record, including the no-control case. Start from
reviewed fe0d360f. Keep the old row191 runner and all historical outputs
immutable; deliver the corrected copy in this packet. Update the cause
call to receive its actual control. Add focused pure-function regression
checks for absent, failed and passing control wording. No policy change:
classification, controls,90second bound and cleanup remain as reviewed.
The original row191 evidence stays frozen. No live files, bus store, units,
provider calls, Docker, browser, credentials or activation action. No push,
force-push, branch deletion or attribution lines. Sage sweeps the pending
canonical191 BUILD.md/SESSIONS/BUILD-LOG edits; Rocko will not commit those.
## Build and acceptance evidence
New isolated worktree /mnt/storage/scratch/rocko-v502, new branch
rocko/vikunja502-20261022, baseline0fa7bf421; link node_modules immediately.
Use scratch TMPDIR and a dead Docker socket for every permitted run.
Write the runner wording regression checks before changing the helper.
Request Sage's slot before any test or mutant run. Proposed focused gate:
whole stop-run.test.mjs, processing.test.mjs and bus tasks.test.mjs singly;
runner wording regression file; replay SR08, KS43, KS44 and KS54 one at a
time with owned process groups and cleanup. The old SR08/KS54 red evidence
is retained, and the new replays must fail by named assertion and exit.
Run broader package suites only if Sage grants them. No carry from old tests
is claimed after these test changes. Grep readers/pins before final manifest.
Packet will contain base, patch, changed paths, candidate hashes, exact run
logs, mutant detections and the corrected runner hash/diff. Source-only
candidate paths are the three test files; runner files are separately hashed
packet deliverables. Sage resolves reviewer/gate ownership and landing.
## Current state
Initial brief state: no tests or mutant runs yet; subsequent results below record the authorized run.
## Sage scope approval and slot: sage-2471 (2026-10-22 04:55:20Z)
Scope above approved as written. Slot order is MVP205 gate,268r4,194,236;
Sage will fit502into a gap and sends explicit CLEAR. Nothing may run yet.
Prepared5-site table:SR08(clock),KS43(direct handler origin),KS44(bodybound),
KS54(runtime cleanup),V502-status(cross-site403→400). The last site proves
the exact status assertion catches a response that the old>=400 accepted.
Each old site statically matches exactly once in this worktree. No mutation
applied and no syntax/test command executed during this preparation.
A successful replay must be an assertion kill and exit, including SR08 and
KS54. Timeout kills do not satisfy this follow-up's fixture-hardening goal.
The new runner's root will point to rocko-v502 when its wording helper is
fixed after the first red. This is runner workspace configuration, not a
policy change. New packet files/evidence remain separate from landed191.
## Focused verification complete: sage-2475
Run04:59:34Z–05:00:52Z on0fa7bf421 in rocko-v502. Expected regression
red is a named assertion: old generic cause falsely claims a passed control.
After the minimal helper/context fix, that regression passes. Whole single
files:stop-run12/12,processingHTTP6/6,bustasks9/9;awk27pass/0fail/0skip.
All4Python regressions pass (missing,failed,passingcontrol and SR08cause).
No unexpected test failures. No ^skip or TAPSKIP lines.
Five mutants are assertion kills,0timeouts:SR08(5.211s),KS43(.161s),
KS44(.156s),KS54(.883s),V502-status(.162s). SR08 now pairs the parent clock
with its real sleep, and KS54 closes its runtime even when the deps assertion
throws; neither requires timeout adjudication. The wrong-status mutant
returns400instead403 and is rejected by the exact fence assertions.
All5candidate code/test hashes restored. Cwd node/chromium/Python sweep[];
slot returned Sage05:01:29Z. No suites,Docker,browser,live actions or provider
calls. The expected red/green run is distinguished from verification failures.
The candidate manifest binds3landed test files plus the corrected owned
runner and its Python regression source,5paths in total. This expands the
brief's initial source-only3path convention solely to bind the two already
in-scope runner deliverables to the same verified candidate. Other packet
files and historical baseline are inventory-hashed,not candidate paths.
RunnerSHA256:c5bdc764d1cfe11752db2df412c96baa4f366236777df1755bb337e9a943ddea.
Runner code changes:truthful control-aware cause, passing actual control to
the renderer, and workspace root set to this isolated worktree. Classification,
90second limit, group cleanup, restore and control policy unchanged.
Earlier row191runner/evidence remain untouched. No code commit,push,live
step or queue move. Sage owns review assignment and integration of502.
## Rebase and commits: sage-2482
No candidate path changed from0fa7bf421 toce6a96ad6. On new branch
rocko/vikunja502-ce6a-20261022, source candidate was committed then rebased
without conflict ontoce6a96ad6. All5blobs remain byte-identical, range-diff
pairs equal and stable patch-id matches. Focused runs carry by Sage's ruling;
no rerun. rebase-proof.json and rebase-range-diff.txt contain exact hashes.
commit.txt names the source candidate commit; the subsequent packet commit
adds evidence only,not candidate source changes. No pending canonical logs
or row191packet files are included. No push; Sage publishes and requests
Dewey review after checking the two-commit range.
+22
View File
@@ -0,0 +1,22 @@
# Vikunja502 focused results
|Check|Pass|Fail|Skip|
|---|---:|---:|---:|
|stop-run whole|12|0|0|
|processing HTTP whole|6|0|0|
|bus tasks whole|9|0|0|
|Python regressions|4|0|0|
Expected wording regression:1assertion red before fix, then green. No unexpected red.
|Mutant|Result|Seconds|Named failing test|
|---|---|---:|---|
|SR08|assertion-killed|5.211|a separate host joins durable first ownership without duplicate signals|
|KS43|assertion-killed|0.161|processing handler independently refuses wrong origin and cross-site before its writer|
|KS44|assertion-killed|0.156|processing write fences refuse cross-site, wrong origin, unnamed host, duplicate identity and bad payload before writer|
|KS54|assertion-killed|0.883|the runtime refuses an invalid adapter and closes a valid one|
|V502-status|assertion-killed|0.162|processing write fences refuse cross-site, wrong origin, unnamed host, duplicate identity and bad payload before writer|
5/5assertionkills,0timeoutkills. No skips. Candidate restored,process sweep[],slotreturned05:01:29Z.
No package suites,Docker,browser or live action under focused grant.
@@ -0,0 +1,52 @@
[
{
"id": "SR08",
"file": "packages/processing-control/src/runtimes.mjs",
"old": "if (winner.id !== claim.id) {",
"new": "if (false) {",
"test": "packages/processing-control/tests/stop-run.test.mjs",
"pattern": "separate host joins durable first ownership",
"check": "cross-process exclusive first owner",
"status": "unrun"
},
{
"id": "KS43",
"file": "packages/webui/src/processing-http.mjs",
"old": "if (one(req, 'origin') !== (preview.origin ?? `https://${preview.host}`)) return refuse(403, 'cross-origin');",
"new": "// skip origin fence",
"test": "packages/webui/tests/processing.test.mjs",
"pattern": "processing handler independently refuses wrong origin and cross-site before its writer",
"check": "same-origin browser write",
"status": "unrun"
},
{
"id": "KS44",
"file": "packages/webui/src/processing-http.mjs",
"old": "if (size > 1024) throw Error();",
"new": "void 0;",
"test": "packages/webui/tests/processing.test.mjs",
"pattern": "processing write fences refuse",
"check": "bounded control payload",
"status": "unrun"
},
{
"id": "KS54",
"file": "packages/bus/src/runtime.mjs",
"old": "adapter = await tasks({ broker, credentials, businesses, processing });",
"new": "adapter = await tasks({ broker, credentials, businesses });",
"test": "packages/bus/tests/tasks.test.mjs",
"pattern": "",
"check": "production tracker injection",
"status": "unrun"
},
{
"id": "V502-status",
"file": "packages/webui/src/processing-http.mjs",
"old": "return refuse(403, 'cross-site')",
"new": "return refuse(400, 'cross-site')",
"test": "packages/webui/tests/processing.test.mjs",
"pattern": "processing write fences refuse",
"check": "exact refusal status rejects a wrong 400 that coarse >=400 accepted",
"status": "unrun"
}
]
@@ -0,0 +1,12 @@
{
"old_base": "0fa7bf421",
"new_base": "ce6a96ad6",
"candidate_paths": [
"agents/rocko/work/vikunja-502/run-mutants.py",
"agents/rocko/work/vikunja-502/tests/test_timeout_cause.py",
"packages/bus/tests/tasks.test.mjs",
"packages/processing-control/tests/stop-run.test.mjs",
"packages/webui/tests/processing.test.mjs"
],
"changed": []
}
+1
View File
@@ -0,0 +1 @@
ce6a96ad6153a0a7a689e6d9d0f625b55eb86b51
+312
View File
@@ -0,0 +1,312 @@
diff --git a/agents/rocko/work/vikunja-502/run-mutants.py b/agents/rocko/work/vikunja-502/run-mutants.py
new file mode 100644
index 000000000..8dce3064d
--- /dev/null
+++ b/agents/rocko/work/vikunja-502/run-mutants.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python3
+"""Run only with Sage's explicit slot grant. Mutations are exact-once and restored."""
+from pathlib import Path
+import argparse, hashlib, json, os, re, signal, subprocess, sys, time
+packet = Path(__file__).resolve().parent
+root = Path('/mnt/storage/scratch/rocko-v502')
+all_sites = json.loads((packet/'all-mutants.json').read_text())
+parser = argparse.ArgumentParser()
+parser.add_argument('ids', nargs='*')
+parser.add_argument('--resume-from')
+parser.add_argument('--control-file')
+parser.add_argument('--adjudicate-timeout')
+args = parser.parse_args()
+sites = all_sites
+if args.resume_from:
+ ids = [s['id'] for s in all_sites]
+ if args.ids or args.resume_from not in ids: raise SystemExit('invalid resume selection')
+ sites = all_sites[ids.index(args.resume_from):]
+elif args.ids:
+ wanted = set(args.ids); sites = [s for s in all_sites if s['id'] in wanted]
+ if {s['id'] for s in sites} != wanted: raise SystemExit('unknown mutant id')
+if args.adjudicate_timeout and not args.control_file:
+ raise SystemExit('timeout adjudication requires an unmutated whole-file control')
+originals = {e['file']: (root/e['file']).read_bytes() for s in sites for e in [s, *s.get('extra_edits', [])]}
+for s in sites:
+ for e in [s, *s.get('extra_edits', [])]:
+ if originals[e['file']].decode().count(e['old']) != 1: raise SystemExit('preflight '+s['id'])
+logs = packet/('resume-mutant-logs' if args.resume_from else 'mutant-logs'); logs.mkdir(exist_ok=True)
+result_file = packet/('resume-mutant-results.json' if args.resume_from else 'mutant-results.json')
+results = []
+controls = {}
+run_id = str(time.time_ns())
+
+def verify_candidate():
+ expected = (packet/'commit.txt').read_text().strip()
+ if subprocess.check_output(['git','rev-parse','HEAD'],cwd=root,text=True).strip()!=expected:
+ raise RuntimeError('candidate HEAD changed')
+ manifest = packet/'candidate-manifest.sha256'
+ digest = hashlib.sha256(manifest.read_bytes()).hexdigest()
+ if digest!=(packet/'candidate.txt').read_text().strip(): raise RuntimeError('manifest digest changed')
+ for line in manifest.read_text().splitlines():
+ wanted,name = line.split(' ',1)
+ if hashlib.sha256((root/name).read_bytes()).hexdigest()!=wanted: raise RuntimeError('candidate drift '+name)
+ return {'head':expected,'manifest':digest}
+
+def stat(pid):
+ try:
+ raw = Path('/proc',str(pid),'stat').read_text(); fields = raw[raw.rfind(')')+2:].split()
+ return dict(ppid=int(fields[1]), group=int(fields[2]), start=fields[19], state=fields[0])
+ except (FileNotFoundError, PermissionError, ProcessLookupError): return None
+def capture(child, known):
+ current = {}
+ for p in Path('/proc').iterdir():
+ if p.name.isdecimal():
+ value=stat(int(p.name))
+ if value: current[int(p.name)]=value
+ changed=True
+ while changed:
+ changed=False
+ for pid,value in current.items():
+ if pid not in known and (pid==child or (value['ppid'] in known and current.get(value['ppid'],{}).get('start')==known[value['ppid']]['start'])): known[pid]=value;changed=True
+
+def cleanup(known):
+ groups={}
+ for pid,old in known.items():
+ cur=stat(pid)
+ if cur and cur['start']==old['start'] and cur['state']!='Z': groups.setdefault(cur['group'],[]).append(pid)
+ for group,anchors in groups.items():
+ # Every member here descends from this test runner, identified by start ticks.
+ if not any((cur:=stat(pid)) and cur['start']==known[pid]['start'] and cur['group']==group for pid in anchors):continue
+ try: os.killpg(group, signal.SIGKILL)
+ except ProcessLookupError: pass
+def execute(test, pattern, log):
+ child = None; known = {}; timedout = False; started = time.monotonic()
+ command = ['node','--test','--test-reporter=tap']
+ if pattern: command += ['--test-name-pattern='+re.escape(pattern)]
+ command += [test]
+ try:
+ with log.open('w') as out:
+ child = subprocess.Popen(command,cwd=root,stdout=out,stderr=subprocess.STDOUT,start_new_session=True,
+ env={**os.environ,'TMPDIR':os.environ['TMPDIR'],'DOCKER_HOST':'unix:///nonexistent.sock',
+ 'MOSAIC_CONFIG':str(Path(os.environ['TMPDIR'])/'no-live-config.json')})
+ first = stat(child.pid)
+ if first: known[child.pid] = first
+ deadline = started+90
+ while child.poll() is None:
+ capture(child.pid,known)
+ if time.monotonic()>=deadline:
+ timedout = True; cleanup(known)
+ try: os.killpg(child.pid,signal.SIGKILL)
+ except ProcessLookupError: pass
+ break
+ time.sleep(.05)
+ rc = child.wait()
+ finally:
+ if child and child.poll() is None:
+ cleanup(known)
+ try: os.killpg(child.pid,signal.SIGKILL)
+ except ProcessLookupError: pass
+ child.wait()
+ cleanup(known)
+ text = log.read_text()
+ return {'exit':rc,'timeout':timedout,'seconds':time.monotonic()-started,'log':str(log),
+ 'failing':re.findall(r'^\s*not ok \d+ - (.*)$',text,re.M),
+ 'counts':{k:int(v) for k,v in re.findall(r'^# (tests|pass|fail|skipped|cancelled) (\d+)\s*$',text,re.M)}}
+
+def timeout_cause(site, control=None):
+ if site['id']=='SR08':
+ return ('runtimes.mjs:333 loops on now() <= deadline; stop-run.test.mjs:106-108 '
+ 'gives the second host a fixed fake clock with a real sleep. Removing the join '
+ 'branch enters the delivery loop without advancing that clock. Original log: TAP header only.')
+ if not control or control.get('passed') is not True:
+ return ('Mutated '+site['file']+' timed out after 90s; no passing same-file control '
+ 'exists in this invocation. Detection is not established. '
+ 'Detailed cause must be recorded before review.')
+ return ('Mutated '+site['file']+' timed out after 90s while the same whole test file '
+ 'passed unmutated in this invocation. Detailed cause must be recorded before review.')
+
+try:
+ binding = verify_candidate()
+ if args.control_file:
+ if args.control_file not in {s['test'] for s in all_sites}: raise SystemExit('unknown control file')
+ before = hashlib.sha256((root/args.control_file).read_bytes()).hexdigest()
+ control = execute(args.control_file,'',logs/'unmutated-control.tap')
+ after = hashlib.sha256((root/args.control_file).read_bytes()).hexdigest()
+ counts = control['counts']
+ passed = (control['exit']==0 and not control['timeout'] and not control['failing'] and before==after
+ and counts.get('tests',0)>0 and counts.get('pass')==counts.get('tests')
+ and counts.get('fail')==0 and counts.get('cancelled')==0 and counts.get('skipped')==0)
+ control.update(run_id=run_id,test=args.control_file,test_sha256=before,passed=passed,**binding)
+ (packet/'unmutated-control.json').write_text(json.dumps(control,indent=2)+'\n')
+ print('CONTROL',json.dumps(control),flush=True)
+ if not passed: raise SystemExit('control failed; stop for Mos, no mutation applied')
+ verify_candidate()
+ controls[args.control_file] = control
+ if args.adjudicate_timeout:
+ # This is the already-observed SR08 timeout, not a rerun or an assertion kill.
+ if args.adjudicate_timeout!='SR08': raise SystemExit('only SR08 historical adjudication approved')
+ prior = json.loads((packet/'mutant-results.json').read_text())
+ old = next((s for s in prior if s['id']=='SR08'),None)
+ site = next(s for s in all_sites if s['id']=='SR08')
+ if not old or old['status']!='timeout' or old['test']!=args.control_file or old['failing']:
+ raise SystemExit('historical timeout does not match approved control')
+ if Path(old['log']).read_text().strip()!='TAP version 13': raise SystemExit('historical timeout log changed')
+ adjudication = {**old,'status':'killed-by-timeout','cause':timeout_cause(site, controls[args.control_file]),'control':controls[args.control_file],
+ 'original_log_sha256':hashlib.sha256(Path(old['log']).read_bytes()).hexdigest()}
+ (packet/'SR08-timeout-adjudication.json').write_text(json.dumps(adjudication,indent=2)+'\n')
+ print('TIMEOUT_KILL SR08',adjudication['cause'],flush=True)
+ for i,s in enumerate(sites,1):
+ verify_candidate()
+ edits = [s, *s.get('extra_edits', [])]
+ for e in edits:
+ if (root/e['file']).read_bytes()!=originals[e['file']]: raise RuntimeError('source drift '+s['id'])
+ print(f'{i}/{len(sites)} {s["id"]}: {s["check"]}',flush=True)
+ try:
+ for e in edits:
+ (root/e['file']).write_bytes(originals[e['file']].decode().replace(e['old'],e['new'],1).encode())
+ observed = execute(s['test'],s['pattern'],logs/(s['id']+'.tap'))
+ finally:
+ for e in edits: (root/e['file']).write_bytes(originals[e['file']])
+ expected = not s['pattern'] or any(s['pattern'] in name for name in observed['failing'])
+ control = controls.get(s['test'])
+ if observed['timeout']:
+ status = 'killed-by-timeout' if control and control['passed'] and control['run_id']==run_id else 'timeout-without-control'
+ elif observed['exit']==0: status = 'survived'
+ elif observed['failing'] and expected: status = 'assertion-killed'
+ else: status = 'invalid-failure'
+ row = {**{k:s[k] for k in ['id','file','test','pattern','check']},**observed,'status':status}
+ if observed['timeout']: row.update(cause=timeout_cause(s, control),control=control)
+ results.append(row)
+ result_file.write_text(json.dumps(results,indent=2)+'\n')
+ print(status,flush=True)
+ if status not in ('assertion-killed','killed-by-timeout'): raise SystemExit(1)
+finally:
+ for name,data in originals.items(): (root/name).write_bytes(data)
+ if any((root/name).read_bytes()!=data for name,data in originals.items()): raise RuntimeError('restore failed')
+summary = {'assertion_kills':sum(s['status']=='assertion-killed' for s in results),
+ 'timeout_kills':sum(s['status']=='killed-by-timeout' for s in results),
+ 'historical_timeout_kills':int(bool(args.adjudicate_timeout)),'selected':len(sites),
+ 'run_id':run_id,'candidate':binding}
+(packet/'mutant-resume-summary.json').write_text(json.dumps(summary,indent=2)+'\n')
+print('MUTANT_SUMMARY',json.dumps(summary),flush=True)
diff --git a/agents/rocko/work/vikunja-502/tests/test_timeout_cause.py b/agents/rocko/work/vikunja-502/tests/test_timeout_cause.py
new file mode 100644
index 000000000..444ec2c5e
--- /dev/null
+++ b/agents/rocko/work/vikunja-502/tests/test_timeout_cause.py
@@ -0,0 +1,44 @@
+"""Exercise the actual pure cause renderer without executing the mutant driver."""
+import ast
+from pathlib import Path
+import unittest
+
+RUNNER = Path(__file__).resolve().parents[1] / 'run-mutants.py'
+
+
+def cause_renderer():
+ tree = ast.parse(RUNNER.read_text())
+ function = next(n for n in tree.body if isinstance(n, ast.FunctionDef) and n.name == 'timeout_cause')
+ namespace = {}
+ exec(compile(ast.Module(body=[function], type_ignores=[]), str(RUNNER), 'exec'), namespace)
+ return namespace['timeout_cause']
+
+
+class TimeoutCause(unittest.TestCase):
+ def setUp(self):
+ self.cause = cause_renderer()
+ self.site = {'id': 'KS54', 'file': 'packages/bus/src/runtime.mjs'}
+
+ def test_missing_control_never_claims_an_unmutated_pass(self):
+ text = self.cause(self.site)
+ self.assertIn('no passing same-file control', text)
+ self.assertNotIn('passed unmutated', text)
+
+ def test_failed_control_never_claims_an_unmutated_pass(self):
+ text = self.cause(self.site, {'passed': False})
+ self.assertIn('no passing same-file control', text)
+ self.assertNotIn('passed unmutated', text)
+
+ def test_passing_control_can_state_its_observed_pass(self):
+ text = self.cause(self.site, {'passed': True})
+ self.assertIn('passed unmutated', text)
+ self.assertNotIn('no passing same-file control', text)
+
+ def test_known_sr08_cause_remains_specific_without_claiming_a_control(self):
+ text = self.cause({'id': 'SR08', 'file': 'packages/processing-control/src/runtimes.mjs'})
+ self.assertIn('fixed fake clock', text)
+ self.assertNotIn('passed unmutated', text)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/packages/bus/tests/tasks.test.mjs b/packages/bus/tests/tasks.test.mjs
index 4c8aae9d6..081925299 100644
--- a/packages/bus/tests/tasks.test.mjs
+++ b/packages/bus/tests/tasks.test.mjs
@@ -259,10 +259,13 @@ test('the runtime refuses an invalid adapter and closes a valid one', async (t)
return { handle: async () => ({ ok: 1 }), timeout: 1000, close: async () => closed++ };
},
});
- // Row 191: runtime injects cancellation alongside the existing factory dependencies.
- assert.deepEqual(given, ['broker', 'businesses', 'credentials', 'processing']);
- assert.equal(typeof processing.work, 'function');
- await runtime.close();
+ try {
+ // Row 191: runtime injects cancellation alongside the existing factory dependencies.
+ assert.deepEqual(given, ['broker', 'businesses', 'credentials', 'processing']);
+ assert.equal(typeof processing.work, 'function');
+ } finally {
+ await runtime.close();
+ }
assert.equal(closed, 5);
await assert.rejects(
startBroker({
diff --git a/packages/processing-control/tests/stop-run.test.mjs b/packages/processing-control/tests/stop-run.test.mjs
index ed69bc111..f54022251 100644
--- a/packages/processing-control/tests/stop-run.test.mjs
+++ b/packages/processing-control/tests/stop-run.test.mjs
@@ -105,7 +105,7 @@ test('a separate host joins durable first ownership without duplicate signals',
const messages = []; child.on('message', value => messages.push(value));
await once(child, 'message');
const joined = stopRun({ ...f.args, owner: { decision: 'second-host' } },
- { ...f.options, sleep: ms => new Promise(resolve => setTimeout(resolve, ms)) });
+ { ...f.options, now: Date.now, sleep: ms => new Promise(resolve => setTimeout(resolve, ms)) });
setTimeout(() => child.send('continue'), 30);
const result = await joined;
const exit = child.exitCode === null ? await once(child, 'exit') : [child.exitCode];
diff --git a/packages/webui/tests/processing.test.mjs b/packages/webui/tests/processing.test.mjs
index e9874602c..14956867b 100644
--- a/packages/webui/tests/processing.test.mjs
+++ b/packages/webui/tests/processing.test.mjs
@@ -52,15 +52,26 @@ test('Console halt and generation-bound resume use only the management writer an
test('processing write fences refuse cross-site, wrong origin, unnamed host, duplicate identity and bad payload before writer', async t => {
const f = await fixture(t);
const cases = [
- { headers: { ...POST.headers, origin: 'https://elsewhere' } },
- { headers: { ...POST.headers, 'sec-fetch-site': 'cross-site' } },
- { headers: { ...POST.headers, host: `127.0.0.1:${f.server.address().port}`, 'x-user': undefined } },
- { headers: { ...POST.headers, 'x-user': ['jason', 'jason'] } },
- { headers: { ...POST.headers, 'content-type': 'text/plain' } },
- { body: '{"human":"jason"}' }, { body: '[]' }, { body: 'null' }, { body: ' '.repeat(1025) + '{}' },
+ ['wrong origin', { headers: { ...POST.headers, origin: 'https://elsewhere' } }, 403, 'cross-origin request refused'],
+ ['cross-site', { headers: { ...POST.headers, 'sec-fetch-site': 'cross-site' } }, 403, 'cross-site'],
+ ['unnamed host', { headers: { ...POST.headers, host: `127.0.0.1:${f.server.address().port}`,
+ origin: `http://127.0.0.1:${f.server.address().port}`, 'x-user': undefined } }, 403, 'read-only'],
+ ['duplicate identity', { headers: { ...POST.headers, 'x-user': ['jason', 'jason'] } }, 401, 'sign-in required'],
+ ['content type', { headers: { ...POST.headers, 'content-type': 'text/plain' } }, 415, 'invalid-request'],
+ ['extra key', { body: '{"human":"jason"}' }, 400, 'invalid-request'],
+ ['array', { body: '[]' }, 400, 'invalid-request'],
+ ['null', { body: 'null' }, 400, 'invalid-request'],
+ ['body bound', { body: ' '.repeat(1025) + '{}' }, 400, 'invalid-request'],
];
- for (const c of cases) assert.ok((await http(f.server, '/api/processing/halt', { ...POST, ...c })).status >= 400);
- assert.equal((await http(f.server, '/api/processing/resume', POST)).status, 400);
+ for (const [name, changed, status, error] of cases) {
+ const response = await http(f.server, '/api/processing/halt', { ...POST, ...changed });
+ assert.equal(response.status, status, name);
+ assert.equal(response.value.error, error, name);
+ assert.deepEqual(f.calls, [], name + ' must refuse before the writer');
+ }
+ const missingGeneration = await http(f.server, '/api/processing/resume', POST);
+ assert.equal(missingGeneration.status, 400);
+ assert.equal(missingGeneration.value.error, 'invalid-request');
assert.deepEqual(f.calls, []);
});
test('tester cannot invoke either control writer and failures never echo backend text', async t => {
@@ -0,0 +1,5 @@
c5bdc764d1cfe11752db2df412c96baa4f366236777df1755bb337e9a943ddea agents/rocko/work/vikunja-502/run-mutants.py
475ab651a60bc4721ba8bb193b3f05ff67d25afc068e80382174b98828ad085d agents/rocko/work/vikunja-502/tests/test_timeout_cause.py
a17ef09d68a13181fb0d27fde9006bd14ecf340270e07d7c79f6b37025f773c8 packages/bus/tests/tasks.test.mjs
bbf88324b1adc9acbb91a49f08d3ae5edb1d65a233933bdb86bc96701b0a35d3 packages/processing-control/tests/stop-run.test.mjs
28caf153da5d16949c5a3e1cedf8c91613e7230840d9df65d5dc4bdc95e72bf5 packages/webui/tests/processing.test.mjs
@@ -0,0 +1 @@
968d6ee345766da838df68f491b5a77c9a25157034dc49e9d4a9b603e4ce7812
@@ -0,0 +1,5 @@
agents/rocko/work/vikunja-502/run-mutants.py
agents/rocko/work/vikunja-502/tests/test_timeout_cause.py
packages/bus/tests/tasks.test.mjs
packages/processing-control/tests/stop-run.test.mjs
packages/webui/tests/processing.test.mjs
+1
View File
@@ -0,0 +1 @@
8e3c6a79bfa39e739bf728f949f51e9842ca0df9
@@ -0,0 +1 @@
[]
@@ -0,0 +1,87 @@
[
{
"step": "runner-red",
"command": [
"python3",
"agents/rocko/work/vikunja-502/tests/test_timeout_cause.py",
"TimeoutCause.test_missing_control_never_claims_an_unmutated_pass"
],
"start": "2026-10-22T04:59:34Z",
"end": "2026-10-22T04:59:34Z",
"exit": 1,
"log": "/mnt/storage/scratch/rocko-v502/agents/rocko/work/vikunja-502/logs/runner-red.log"
},
{
"step": "runner-green",
"command": [
"python3",
"agents/rocko/work/vikunja-502/tests/test_timeout_cause.py",
"TimeoutCause.test_missing_control_never_claims_an_unmutated_pass"
],
"start": "2026-10-22T05:00:01Z",
"end": "2026-10-22T05:00:01Z",
"exit": 0,
"log": "/mnt/storage/scratch/rocko-v502/agents/rocko/work/vikunja-502/logs/runner-green.log"
},
{
"step": "stop-run-whole",
"command": [
"node",
"--test",
"--test-reporter=tap",
"packages/processing-control/tests/stop-run.test.mjs"
],
"start": "2026-10-22T05:00:01Z",
"end": "2026-10-22T05:00:01Z",
"exit": 0,
"log": "/mnt/storage/scratch/rocko-v502/agents/rocko/work/vikunja-502/logs/stop-run-whole.log"
},
{
"step": "processing-http-whole",
"command": [
"node",
"--test",
"--test-reporter=tap",
"packages/webui/tests/processing.test.mjs"
],
"start": "2026-10-22T05:00:02Z",
"end": "2026-10-22T05:00:02Z",
"exit": 0,
"log": "/mnt/storage/scratch/rocko-v502/agents/rocko/work/vikunja-502/logs/processing-http-whole.log"
},
{
"step": "bus-tasks-whole",
"command": [
"node",
"--test",
"--test-reporter=tap",
"packages/bus/tests/tasks.test.mjs"
],
"start": "2026-10-22T05:00:02Z",
"end": "2026-10-22T05:00:03Z",
"exit": 0,
"log": "/mnt/storage/scratch/rocko-v502/agents/rocko/work/vikunja-502/logs/bus-tasks-whole.log"
},
{
"step": "python-regressions",
"command": [
"python3",
"agents/rocko/work/vikunja-502/tests/test_timeout_cause.py"
],
"start": "2026-10-22T05:00:03Z",
"end": "2026-10-22T05:00:03Z",
"exit": 0,
"log": "/mnt/storage/scratch/rocko-v502/agents/rocko/work/vikunja-502/logs/python-regressions.log"
},
{
"step": "replays",
"command": [
"python3",
"agents/rocko/work/vikunja-502/run-mutants.py"
],
"start": "2026-10-22T05:00:45Z",
"end": "2026-10-22T05:00:52Z",
"exit": 0,
"log": "/mnt/storage/scratch/rocko-v502/agents/rocko/work/vikunja-502/logs/replays.log"
}
]
@@ -0,0 +1,29 @@
{
"status": "focused scope completed under sage-2475; no further run authorized",
"scope": "sage-2471",
"worktree": "/mnt/storage/scratch/rocko-v502",
"base": "0fa7bf421fc153a226b5f06ed484e089e48e23ba",
"red_command": [
"python3",
"/mnt/storage/src/mosaic-stack/agents/rocko/work/vikunja-502/tests/test_timeout_cause.py",
"TimeoutCause.test_missing_control_never_claims_an_unmutated_pass"
],
"green_command": [
"python3",
"/mnt/storage/src/mosaic-stack/agents/rocko/work/vikunja-502/tests/test_timeout_cause.py"
],
"single_files": [
"packages/processing-control/tests/stop-run.test.mjs",
"packages/webui/tests/processing.test.mjs",
"packages/bus/tests/tasks.test.mjs"
],
"replay_ids": [
"SR08",
"KS43",
"KS44",
"KS54",
"V502-status"
],
"replay_required_status": "assertion-killed for every site; no timeout kills accepted as fixture-hardening evidence",
"process_rules": "short scratch TMPDIR,deadDocker,one test file at a time,owned process groups,timeouts,restore and cwd sweep"
}
@@ -0,0 +1,64 @@
TAP version 13
# Subtest: recordTask keeps sync reads and a role write apart
ok 1 - recordTask keeps sync reads and a role write apart
---
duration_ms: 22.077964
type: 'test'
...
# Subtest: read_at must be one canonical UTC format, so the projection compares strings safely
ok 2 - read_at must be one canonical UTC format, so the projection compares strings safely
---
duration_ms: 9.65343
type: 'test'
...
# Subtest: a bad entry refuses the whole record
ok 3 - a bad entry refuses the whole record
---
duration_ms: 10.008621
type: 'test'
...
# Subtest: taskView reads the projection for one business
ok 4 - taskView reads the projection for one business
---
duration_ms: 11.605317
type: 'test'
...
# Subtest: requestTask hands only a holder and a task verb to the handler, and records refusals
ok 5 - requestTask hands only a holder and a task verb to the handler, and records refusals
---
duration_ms: 21.879126
type: 'test'
...
# Subtest: the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous
ok 6 - the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous
---
duration_ms: 294.372769
type: 'test'
...
# Subtest: without an adapter the server refuses every task verb
ok 7 - without an adapter the server refuses every task verb
---
duration_ms: 39.665659
type: 'test'
...
# Subtest: the runtime refuses an invalid adapter and closes a valid one
ok 8 - the runtime refuses an invalid adapter and closes a valid one
---
duration_ms: 51.674609
type: 'test'
...
# Subtest: the process loads the S3 adapter from plain-data trackers
ok 9 - the process loads the S3 adapter from plain-data trackers
---
duration_ms: 248.929804
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 9
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 819.657876
@@ -0,0 +1,46 @@
TAP version 13
# Subtest: read-only Console can read processing state but neither human control writes without --send
ok 1 - read-only Console can read processing state but neither human control writes without --send
---
duration_ms: 17.100325
type: 'test'
...
# Subtest: Console halt and generation-bound resume use only the management writer and keep partial stops honest
ok 2 - Console halt and generation-bound resume use only the management writer and keep partial stops honest
---
duration_ms: 3.741854
type: 'test'
...
# Subtest: processing write fences refuse cross-site, wrong origin, unnamed host, duplicate identity and bad payload before writer
ok 3 - processing write fences refuse cross-site, wrong origin, unnamed host, duplicate identity and bad payload before writer
---
duration_ms: 5.750951
type: 'test'
...
# Subtest: tester cannot invoke either control writer and failures never echo backend text
ok 4 - tester cannot invoke either control writer and failures never echo backend text
---
duration_ms: 5.062465
type: 'test'
...
# Subtest: processing writes use the existing SSO signer and still require --send and same-origin
ok 5 - processing writes use the existing SSO signer and still require --send and same-origin
---
duration_ms: 4.114043
type: 'test'
...
# Subtest: processing handler independently refuses wrong origin and cross-site before its writer
ok 6 - processing handler independently refuses wrong origin and cross-site before its writer
---
duration_ms: 0.618941
type: 'test'
...
1..6
# tests 6
# suites 0
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 134.607131
@@ -0,0 +1,5 @@
....
----------------------------------------------------------------------
Ran 4 tests in 0.011s
OK
@@ -0,0 +1,11 @@
1/5 SR08: cross-process exclusive first owner
assertion-killed
2/5 KS43: same-origin browser write
assertion-killed
3/5 KS44: bounded control payload
assertion-killed
4/5 KS54: production tracker injection
assertion-killed
5/5 V502-status: exact refusal status rejects a wrong 400 that coarse >=400 accepted
assertion-killed
MUTANT_SUMMARY {"assertion_kills": 5, "timeout_kills": 0, "historical_timeout_kills": 0, "selected": 5, "run_id": "1792645245774744257", "candidate": {"head": "0fa7bf421fc153a226b5f06ed484e089e48e23ba", "manifest": "968d6ee345766da838df68f491b5a77c9a25157034dc49e9d4a9b603e4ce7812"}}
@@ -0,0 +1,5 @@
.
----------------------------------------------------------------------
Ran 1 test in 0.004s
OK
@@ -0,0 +1,13 @@
F
======================================================================
FAIL: test_missing_control_never_claims_an_unmutated_pass (__main__.TimeoutCause.test_missing_control_never_claims_an_unmutated_pass)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/mnt/storage/scratch/rocko-v502/agents/rocko/work/vikunja-502/tests/test_timeout_cause.py", line 24, in test_missing_control_never_claims_an_unmutated_pass
self.assertIn('no passing same-file control', text)
AssertionError: 'no passing same-file control' not found in 'Mutated packages/bus/src/runtime.mjs timed out after 90s while the same whole test file passed unmutated in this invocation. Detailed cause must be recorded before review.'
----------------------------------------------------------------------
Ran 1 test in 0.003s
FAILED (failures=1)
@@ -0,0 +1,82 @@
TAP version 13
# Subtest: unbound run stops by verified registry identity, fixed 5s grace and 2s observe policy
ok 1 - unbound run stops by verified registry identity, fixed 5s grace and 2s observe policy
---
duration_ms: 41.095674
type: 'test'
...
# Subtest: same run joins the identical promise and completed repeats preserve owner and send no signals
ok 2 - same run joins the identical promise and completed repeats preserve owner and send no signals
---
duration_ms: 32.241062
type: 'test'
...
# Subtest: identity changed before TERM or between TERM and KILL refuses the changed signal
ok 3 - identity changed before TERM or between TERM and KILL refuses the changed signal
---
duration_ms: 13.366003
type: 'test'
...
# Subtest: failed signal delivery observes alive, records no invented signals and never restarts escalation
ok 4 - failed signal delivery observes alive, records no invented signals and never restarts escalation
---
duration_ms: 29.310841
type: 'test'
...
# Subtest: missing run and another business never borrow a pid or signal
ok 5 - missing run and another business never borrow a pid or signal
---
duration_ms: 8.032376
type: 'test'
...
# Subtest: halt joins an in-progress fire without another TERM or deadline and links its first receipt
ok 6 - halt joins an in-progress fire without another TERM or deadline and links its first receipt
---
duration_ms: 26.551382
type: 'test'
...
# Subtest: a separate host joins durable first ownership without duplicate signals
ok 7 - a separate host joins durable first ownership without duplicate signals
---
duration_ms: 99.31242
type: 'test'
...
# Subtest: corrupt and symlinked final receipts refuse as unknown rather than signalling again
ok 8 - corrupt and symlinked final receipts refuse as unknown rather than signalling again
---
duration_ms: 24.064935
type: 'test'
...
# Subtest: a later exit gets separate halt evidence while the original alive result stays immutable
ok 9 - a later exit gets separate halt evidence while the original alive result stays immutable
---
duration_ms: 58.520012
type: 'test'
...
# Subtest: root aliases join but a different root never shares the run stop
ok 10 - root aliases join but a different root never shares the run stop
---
duration_ms: 44.780832
type: 'test'
...
# Subtest: a changed runtime record cannot replace the original stop identity after TERM
ok 11 - a changed runtime record cannot replace the original stop identity after TERM
---
duration_ms: 7.48526
type: 'test'
...
# Subtest: identity is rechecked at actual signal delivery, not just at the loop observation
ok 12 - identity is rechecked at actual signal delivery, not just at the loop observation
---
duration_ms: 6.177784
type: 'test'
...
1..12
# tests 12
# suites 0
# pass 12
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 446.315604
@@ -0,0 +1,33 @@
TAP version 13
# Subtest: processing handler independently refuses wrong origin and cross-site before its writer
not ok 1 - processing handler independently refuses wrong origin and cross-site before its writer
---
duration_ms: 4.161227
type: 'test'
location: '/mnt/storage/scratch/rocko-v502/packages/webui/tests/processing.test.mjs:106:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
503 !== 403
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 403
actual: 503
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-v502/packages/webui/tests/processing.test.mjs:118:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 113.430641
@@ -0,0 +1,33 @@
TAP version 13
# Subtest: processing write fences refuse cross-site, wrong origin, unnamed host, duplicate identity and bad payload before writer
not ok 1 - processing write fences refuse cross-site, wrong origin, unnamed host, duplicate identity and bad payload before writer
---
duration_ms: 22.664684
type: 'test'
location: '/mnt/storage/scratch/rocko-v502/packages/webui/tests/processing.test.mjs:52:1'
failureType: 'testCodeFailure'
error: |-
body bound
200 !== 400
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 400
actual: 200
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-v502/packages/webui/tests/processing.test.mjs:68:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 126.507861
@@ -0,0 +1,94 @@
TAP version 13
# Subtest: recordTask keeps sync reads and a role write apart
ok 1 - recordTask keeps sync reads and a role write apart
---
duration_ms: 23.452757
type: 'test'
...
# Subtest: read_at must be one canonical UTC format, so the projection compares strings safely
ok 2 - read_at must be one canonical UTC format, so the projection compares strings safely
---
duration_ms: 9.929636
type: 'test'
...
# Subtest: a bad entry refuses the whole record
ok 3 - a bad entry refuses the whole record
---
duration_ms: 9.894173
type: 'test'
...
# Subtest: taskView reads the projection for one business
ok 4 - taskView reads the projection for one business
---
duration_ms: 11.839691
type: 'test'
...
# Subtest: requestTask hands only a holder and a task verb to the handler, and records refusals
ok 5 - requestTask hands only a holder and a task verb to the handler, and records refusals
---
duration_ms: 23.330397
type: 'test'
...
# Subtest: the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous
ok 6 - the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous
---
duration_ms: 297.537138
type: 'test'
...
# Subtest: without an adapter the server refuses every task verb
ok 7 - without an adapter the server refuses every task verb
---
duration_ms: 43.546083
type: 'test'
...
# Subtest: the runtime refuses an invalid adapter and closes a valid one
not ok 8 - the runtime refuses an invalid adapter and closes a valid one
---
duration_ms: 50.654664
type: 'test'
location: '/mnt/storage/scratch/rocko-v502/packages/bus/tests/tasks.test.mjs:239:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
[
'broker',
'businesses',
'credentials',
- 'processing'
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0: 'broker'
1: 'businesses'
2: 'credentials'
3: 'processing'
actual:
0: 'broker'
1: 'businesses'
2: 'credentials'
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-v502/packages/bus/tests/tasks.test.mjs:264:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: the process loads the S3 adapter from plain-data trackers
ok 9 - the process loads the S3 adapter from plain-data trackers
---
duration_ms: 249.07671
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 829.345079
@@ -0,0 +1,39 @@
TAP version 13
# Subtest: a separate host joins durable first ownership without duplicate signals
not ok 1 - a separate host joins durable first ownership without duplicate signals
---
duration_ms: 5083.453611
type: 'test'
location: '/mnt/storage/scratch/rocko-v502/packages/processing-control/tests/stop-run.test.mjs:90:1'
failureType: 'testCodeFailure'
error: |-
the second host sends no signal
+ actual - expected
+ [
+ 'SIGTERM',
+ 'SIGKILL'
+ ]
- []
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
actual:
0: 'SIGTERM'
1: 'SIGKILL'
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-v502/packages/processing-control/tests/stop-run.test.mjs:113:10)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5132.608875
@@ -0,0 +1,33 @@
TAP version 13
# Subtest: processing write fences refuse cross-site, wrong origin, unnamed host, duplicate identity and bad payload before writer
not ok 1 - processing write fences refuse cross-site, wrong origin, unnamed host, duplicate identity and bad payload before writer
---
duration_ms: 19.005372
type: 'test'
location: '/mnt/storage/scratch/rocko-v502/packages/webui/tests/processing.test.mjs:52:1'
failureType: 'testCodeFailure'
error: |-
cross-site
400 !== 403
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 403
actual: 400
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-v502/packages/webui/tests/processing.test.mjs:68:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 130.795212
@@ -0,0 +1,112 @@
[
{
"id": "SR08",
"file": "packages/processing-control/src/runtimes.mjs",
"test": "packages/processing-control/tests/stop-run.test.mjs",
"pattern": "separate host joins durable first ownership",
"check": "cross-process exclusive first owner",
"exit": 1,
"timeout": false,
"seconds": 5.2106276580598205,
"log": "/mnt/storage/scratch/rocko-v502/agents/rocko/work/vikunja-502/mutant-logs/SR08.tap",
"failing": [
"a separate host joins durable first ownership without duplicate signals"
],
"counts": {
"tests": 1,
"pass": 0,
"fail": 1,
"cancelled": 0,
"skipped": 0
},
"status": "assertion-killed"
},
{
"id": "KS43",
"file": "packages/webui/src/processing-http.mjs",
"test": "packages/webui/tests/processing.test.mjs",
"pattern": "processing handler independently refuses wrong origin and cross-site before its writer",
"check": "same-origin browser write",
"exit": 1,
"timeout": false,
"seconds": 0.16104768705554307,
"log": "/mnt/storage/scratch/rocko-v502/agents/rocko/work/vikunja-502/mutant-logs/KS43.tap",
"failing": [
"processing handler independently refuses wrong origin and cross-site before its writer"
],
"counts": {
"tests": 1,
"pass": 0,
"fail": 1,
"cancelled": 0,
"skipped": 0
},
"status": "assertion-killed"
},
{
"id": "KS44",
"file": "packages/webui/src/processing-http.mjs",
"test": "packages/webui/tests/processing.test.mjs",
"pattern": "processing write fences refuse",
"check": "bounded control payload",
"exit": 1,
"timeout": false,
"seconds": 0.15614235494285822,
"log": "/mnt/storage/scratch/rocko-v502/agents/rocko/work/vikunja-502/mutant-logs/KS44.tap",
"failing": [
"processing write fences refuse cross-site, wrong origin, unnamed host, duplicate identity and bad payload before writer"
],
"counts": {
"tests": 1,
"pass": 0,
"fail": 1,
"cancelled": 0,
"skipped": 0
},
"status": "assertion-killed"
},
{
"id": "KS54",
"file": "packages/bus/src/runtime.mjs",
"test": "packages/bus/tests/tasks.test.mjs",
"pattern": "",
"check": "production tracker injection",
"exit": 1,
"timeout": false,
"seconds": 0.8833191560115665,
"log": "/mnt/storage/scratch/rocko-v502/agents/rocko/work/vikunja-502/mutant-logs/KS54.tap",
"failing": [
"the runtime refuses an invalid adapter and closes a valid one"
],
"counts": {
"tests": 9,
"pass": 8,
"fail": 1,
"cancelled": 0,
"skipped": 0
},
"status": "assertion-killed"
},
{
"id": "V502-status",
"file": "packages/webui/src/processing-http.mjs",
"test": "packages/webui/tests/processing.test.mjs",
"pattern": "processing write fences refuse",
"check": "exact refusal status rejects a wrong 400 that coarse >=400 accepted",
"exit": 1,
"timeout": false,
"seconds": 0.16231847403105348,
"log": "/mnt/storage/scratch/rocko-v502/agents/rocko/work/vikunja-502/mutant-logs/V502-status.tap",
"failing": [
"processing write fences refuse cross-site, wrong origin, unnamed host, duplicate identity and bad payload before writer"
],
"counts": {
"tests": 1,
"pass": 0,
"fail": 1,
"cancelled": 0,
"skipped": 0
},
"status": "assertion-killed"
}
]
@@ -0,0 +1,11 @@
{
"assertion_kills": 5,
"timeout_kills": 0,
"historical_timeout_kills": 0,
"selected": 5,
"run_id": "1792645245774744257",
"candidate": {
"head": "0fa7bf421fc153a226b5f06ed484e089e48e23ba",
"manifest": "968d6ee345766da838df68f491b5a77c9a25157034dc49e9d4a9b603e4ce7812"
}
}
@@ -0,0 +1,39 @@
9dbbf20dc65be5306b1a4fb1259bfcf179bc748390f5b206ca42012f9c8ca078 agents/rocko/work/vikunja-502/BUILD.md
8a5e6ea781f413f838aa9f0f3b49364202600aac4d095c9a22b52e959d68de66 agents/rocko/work/vikunja-502/RESULTS.md
501bbda0c8c33f37857856345322a020af0f77e91d86860f1ac0d0244f708cd5 agents/rocko/work/vikunja-502/all-mutants.json
011d0877029723282896967d6fcd0c6f6c1eff532e5a841b43eda16189395e8f agents/rocko/work/vikunja-502/base-drift-ce6a.json
d0c009ec9f9cad210038db986fc135c7aa358ab96a1c61c78dce4543333bc737 agents/rocko/work/vikunja-502/base.txt
d66e23404156a941f0d4f8d97a83c3de8ba2a243bedccf511462053254542ec9 agents/rocko/work/vikunja-502/build.patch
968d6ee345766da838df68f491b5a77c9a25157034dc49e9d4a9b603e4ce7812 agents/rocko/work/vikunja-502/candidate-manifest.sha256
62ebb87cb798e5c96fed4354399a5252861bf57621fd8bc21d552adfe3a28ab4 agents/rocko/work/vikunja-502/candidate.txt
ebaa1dd2a828fd591a625f1e9bfa7b853c3a1e886bd538fe2de4c26d4beab044 agents/rocko/work/vikunja-502/changed-paths.txt
edd003aa45d2b541012de68a8e7869c47c388e8270bc06adca804bf645c3ff86 agents/rocko/work/vikunja-502/commit.txt
37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570 agents/rocko/work/vikunja-502/cwd-sweep.json
0e5426a7de4dc633a11c15ca9a22812c5ccb7300e1bf02a806c0556b866c7f79 agents/rocko/work/vikunja-502/focused-results.json
7a0d496a5b7babf07e04e31991b804759510b40d58f9573b0b78c5e91e4b3b18 agents/rocko/work/vikunja-502/gate-plan.json
0b91b9016d0da6dc94b496447c4624bac9eefa486d3eba9ea4e733987d13f753 agents/rocko/work/vikunja-502/logs/bus-tasks-whole.log
4245c5f91bfa421c5f697841a93a679afc1d901762c72409830460efa4d6116b agents/rocko/work/vikunja-502/logs/processing-http-whole.log
d52cbb1058abf83e3acb402d1c2b8ee089749613d6075f5d973dd66c8ef09200 agents/rocko/work/vikunja-502/logs/python-regressions.log
bbc1528eeeb64ce52b2532db1e0e798eded967fd66c805a030d35ef34d605ca9 agents/rocko/work/vikunja-502/logs/replays.log
a626407facf1102b512cc38f00f6e7d889aa6280a5c03d1722ac6d1c4eb08dc5 agents/rocko/work/vikunja-502/logs/runner-green.log
23ffbda3d00898ea85570996abad994c521b9500698b36bd8481622f5943e161 agents/rocko/work/vikunja-502/logs/runner-red.log
7c52ba2561dd20f8bdc272647ac6a6cb6358a3005d0cf66ef38914e7d98a173d agents/rocko/work/vikunja-502/logs/stop-run-whole.log
5e0bde2f8340fa3ae0660f08ac1eab29aae0f84856d72df6784b76e0c61110c4 agents/rocko/work/vikunja-502/mutant-logs/KS43.tap
4d57c485599655d5f5c4b3e6d42f24d5dfd32553af3d4993a0b287d939b4be90 agents/rocko/work/vikunja-502/mutant-logs/KS44.tap
5d91c5a8380f7d4f8a9bb8677561bc6edf0c9444eb780e502d5b1dc1fa9f1bcc agents/rocko/work/vikunja-502/mutant-logs/KS54.tap
f195652fa91e8b07c46128ac809b8ec458632f00bced4f64bf8846761ea91ba1 agents/rocko/work/vikunja-502/mutant-logs/SR08.tap
ac40be01b48349b3b3545a92a16576951e1481513bea4aeb5526e66ca6ecbe6e agents/rocko/work/vikunja-502/mutant-logs/V502-status.tap
46306ee1d3a681e6fb1e61d2887b3b62a4832f3617f51209ee8ec8e4c07bc0b7 agents/rocko/work/vikunja-502/mutant-results.json
286e53b9cd0849db12da5fb4113aab2c9c89a49341d1661fe70b23c757ae3b51 agents/rocko/work/vikunja-502/mutant-resume-summary.json
730813393354bb940f766f4894fde1a4dffe6fe4afbcc9d2df31fa4a829da2cd agents/rocko/work/vikunja-502/pre-rebase-commit.txt
d44d16850224227a86842652fd0f9071592e330f580852db5cfce5c60455c727 agents/rocko/work/vikunja-502/rebase-proof.json
a4dd7e62f35597edef0fc970e8fe9e5be3f4ff15feac61863b02735bc3d8c6a4 agents/rocko/work/vikunja-502/rebase-range-diff.txt
bdae7b9f187f9945828e6b164f70e4f7a73ecbcafbfe780727e91ea3dbdc9c83 agents/rocko/work/vikunja-502/restored-candidate.json
fe0d360f86d86ab2df967da5338479c593c27c88d06eb38a1389d99a08489369 agents/rocko/work/vikunja-502/run-mutants.baseline.py
c5bdc764d1cfe11752db2df412c96baa4f366236777df1755bb337e9a943ddea agents/rocko/work/vikunja-502/run-mutants.py
708397d67087d30bbfacdd27d0dfd81055aa47d841cbcbc8281bc4bb98632f21 agents/rocko/work/vikunja-502/runner-change.diff
c68bc5fb03bfec8ef2cb1559e8d7bfc67e2460c41828e55c9abd6e21a355f3f1 agents/rocko/work/vikunja-502/runner.sha256
041a6eed7c60eb2f6900974718ed8db85efb0178afe71ca5ce828f0e89588b96 agents/rocko/work/vikunja-502/scan-summary.json
37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570 agents/rocko/work/vikunja-502/skips.json
475ab651a60bc4721ba8bb193b3f05ff67d25afc068e80382174b98828ad085d agents/rocko/work/vikunja-502/tests/test_timeout_cause.py
9532b4498e2aa1ad91b37aa032a8305c338b6b9953728e2d88b5ae0071647097 agents/rocko/work/vikunja-502/tmpdir.txt
@@ -0,0 +1 @@
3b04790e28a2390a9b3c9cfad71ab63acc35e0ce
@@ -0,0 +1,12 @@
{
"old_base": "0fa7bf421",
"old_commit": "3b04790e28a2390a9b3c9cfad71ab63acc35e0ce",
"new_base": "ce6a96ad6153a0a7a689e6d9d0f625b55eb86b51",
"new_commit": "8e3c6a79bfa39e739bf728f949f51e9842ca0df9",
"paths": 5,
"blob_drift": [],
"old_patch_id": "37135ebe64f1e9b40f952f4b7047fbd31db58362",
"new_patch_id": "37135ebe64f1e9b40f952f4b7047fbd31db58362",
"range_diff": "equal",
"verification_carries": "sage-2482"
}
@@ -0,0 +1 @@
1: 3b04790e2 = 1: 8e3c6a79b test: harden processing failure diagnostics and cleanup
@@ -0,0 +1,4 @@
{
"paths": 5,
"drift": []
}
@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""Run only with Sage's explicit slot grant. Mutations are exact-once and restored."""
from pathlib import Path
import argparse, hashlib, json, os, re, signal, subprocess, sys, time
packet = Path(__file__).resolve().parent
root = Path('/mnt/storage/scratch/rocko-r191-r3')
all_sites = json.loads((packet/'all-mutants.json').read_text())
parser = argparse.ArgumentParser()
parser.add_argument('ids', nargs='*')
parser.add_argument('--resume-from')
parser.add_argument('--control-file')
parser.add_argument('--adjudicate-timeout')
args = parser.parse_args()
sites = all_sites
if args.resume_from:
ids = [s['id'] for s in all_sites]
if args.ids or args.resume_from not in ids: raise SystemExit('invalid resume selection')
sites = all_sites[ids.index(args.resume_from):]
elif args.ids:
wanted = set(args.ids); sites = [s for s in all_sites if s['id'] in wanted]
if {s['id'] for s in sites} != wanted: raise SystemExit('unknown mutant id')
if args.adjudicate_timeout and not args.control_file:
raise SystemExit('timeout adjudication requires an unmutated whole-file control')
originals = {e['file']: (root/e['file']).read_bytes() for s in sites for e in [s, *s.get('extra_edits', [])]}
for s in sites:
for e in [s, *s.get('extra_edits', [])]:
if originals[e['file']].decode().count(e['old']) != 1: raise SystemExit('preflight '+s['id'])
logs = packet/('resume-mutant-logs' if args.resume_from else 'mutant-logs'); logs.mkdir(exist_ok=True)
result_file = packet/('resume-mutant-results.json' if args.resume_from else 'mutant-results.json')
results = []
controls = {}
run_id = str(time.time_ns())
def verify_candidate():
expected = (packet/'commit.txt').read_text().strip()
if subprocess.check_output(['git','rev-parse','HEAD'],cwd=root,text=True).strip()!=expected:
raise RuntimeError('candidate HEAD changed')
manifest = packet/'candidate-manifest.sha256'
digest = hashlib.sha256(manifest.read_bytes()).hexdigest()
if digest!=(packet/'candidate.txt').read_text().strip(): raise RuntimeError('manifest digest changed')
for line in manifest.read_text().splitlines():
wanted,name = line.split(' ',1)
if hashlib.sha256((root/name).read_bytes()).hexdigest()!=wanted: raise RuntimeError('candidate drift '+name)
return {'head':expected,'manifest':digest}
def stat(pid):
try:
raw = Path('/proc',str(pid),'stat').read_text(); fields = raw[raw.rfind(')')+2:].split()
return dict(ppid=int(fields[1]), group=int(fields[2]), start=fields[19], state=fields[0])
except (FileNotFoundError, PermissionError, ProcessLookupError): return None
def capture(child, known):
current = {}
for p in Path('/proc').iterdir():
if p.name.isdecimal():
value=stat(int(p.name))
if value: current[int(p.name)]=value
changed=True
while changed:
changed=False
for pid,value in current.items():
if pid not in known and (pid==child or (value['ppid'] in known and current.get(value['ppid'],{}).get('start')==known[value['ppid']]['start'])): known[pid]=value;changed=True
def cleanup(known):
groups={}
for pid,old in known.items():
cur=stat(pid)
if cur and cur['start']==old['start'] and cur['state']!='Z': groups.setdefault(cur['group'],[]).append(pid)
for group,anchors in groups.items():
# Every member here descends from this test runner, identified by start ticks.
if not any((cur:=stat(pid)) and cur['start']==known[pid]['start'] and cur['group']==group for pid in anchors):continue
try: os.killpg(group, signal.SIGKILL)
except ProcessLookupError: pass
def execute(test, pattern, log):
child = None; known = {}; timedout = False; started = time.monotonic()
command = ['node','--test','--test-reporter=tap']
if pattern: command += ['--test-name-pattern='+re.escape(pattern)]
command += [test]
try:
with log.open('w') as out:
child = subprocess.Popen(command,cwd=root,stdout=out,stderr=subprocess.STDOUT,start_new_session=True,
env={**os.environ,'TMPDIR':os.environ['TMPDIR'],'DOCKER_HOST':'unix:///nonexistent.sock',
'MOSAIC_CONFIG':str(Path(os.environ['TMPDIR'])/'no-live-config.json')})
first = stat(child.pid)
if first: known[child.pid] = first
deadline = started+90
while child.poll() is None:
capture(child.pid,known)
if time.monotonic()>=deadline:
timedout = True; cleanup(known)
try: os.killpg(child.pid,signal.SIGKILL)
except ProcessLookupError: pass
break
time.sleep(.05)
rc = child.wait()
finally:
if child and child.poll() is None:
cleanup(known)
try: os.killpg(child.pid,signal.SIGKILL)
except ProcessLookupError: pass
child.wait()
cleanup(known)
text = log.read_text()
return {'exit':rc,'timeout':timedout,'seconds':time.monotonic()-started,'log':str(log),
'failing':re.findall(r'^\s*not ok \d+ - (.*)$',text,re.M),
'counts':{k:int(v) for k,v in re.findall(r'^# (tests|pass|fail|skipped|cancelled) (\d+)\s*$',text,re.M)}}
def timeout_cause(site):
if site['id']=='SR08':
return ('runtimes.mjs:333 loops on now() <= deadline; stop-run.test.mjs:106-108 '
'gives the second host a fixed fake clock with a real sleep. Removing the join '
'branch enters the delivery loop without advancing that clock. Original log: TAP header only.')
return ('Mutated '+site['file']+' timed out after 90s while the same whole test file '
'passed unmutated in this invocation. Detailed cause must be recorded before review.')
try:
binding = verify_candidate()
if args.control_file:
if args.control_file not in {s['test'] for s in all_sites}: raise SystemExit('unknown control file')
before = hashlib.sha256((root/args.control_file).read_bytes()).hexdigest()
control = execute(args.control_file,'',logs/'unmutated-control.tap')
after = hashlib.sha256((root/args.control_file).read_bytes()).hexdigest()
counts = control['counts']
passed = (control['exit']==0 and not control['timeout'] and not control['failing'] and before==after
and counts.get('tests',0)>0 and counts.get('pass')==counts.get('tests')
and counts.get('fail')==0 and counts.get('cancelled')==0 and counts.get('skipped')==0)
control.update(run_id=run_id,test=args.control_file,test_sha256=before,passed=passed,**binding)
(packet/'unmutated-control.json').write_text(json.dumps(control,indent=2)+'\n')
print('CONTROL',json.dumps(control),flush=True)
if not passed: raise SystemExit('control failed; stop for Mos, no mutation applied')
verify_candidate()
controls[args.control_file] = control
if args.adjudicate_timeout:
# This is the already-observed SR08 timeout, not a rerun or an assertion kill.
if args.adjudicate_timeout!='SR08': raise SystemExit('only SR08 historical adjudication approved')
prior = json.loads((packet/'mutant-results.json').read_text())
old = next((s for s in prior if s['id']=='SR08'),None)
site = next(s for s in all_sites if s['id']=='SR08')
if not old or old['status']!='timeout' or old['test']!=args.control_file or old['failing']:
raise SystemExit('historical timeout does not match approved control')
if Path(old['log']).read_text().strip()!='TAP version 13': raise SystemExit('historical timeout log changed')
adjudication = {**old,'status':'killed-by-timeout','cause':timeout_cause(site),'control':controls[args.control_file],
'original_log_sha256':hashlib.sha256(Path(old['log']).read_bytes()).hexdigest()}
(packet/'SR08-timeout-adjudication.json').write_text(json.dumps(adjudication,indent=2)+'\n')
print('TIMEOUT_KILL SR08',adjudication['cause'],flush=True)
for i,s in enumerate(sites,1):
verify_candidate()
edits = [s, *s.get('extra_edits', [])]
for e in edits:
if (root/e['file']).read_bytes()!=originals[e['file']]: raise RuntimeError('source drift '+s['id'])
print(f'{i}/{len(sites)} {s["id"]}: {s["check"]}',flush=True)
try:
for e in edits:
(root/e['file']).write_bytes(originals[e['file']].decode().replace(e['old'],e['new'],1).encode())
observed = execute(s['test'],s['pattern'],logs/(s['id']+'.tap'))
finally:
for e in edits: (root/e['file']).write_bytes(originals[e['file']])
expected = not s['pattern'] or any(s['pattern'] in name for name in observed['failing'])
control = controls.get(s['test'])
if observed['timeout']:
status = 'killed-by-timeout' if control and control['passed'] and control['run_id']==run_id else 'timeout-without-control'
elif observed['exit']==0: status = 'survived'
elif observed['failing'] and expected: status = 'assertion-killed'
else: status = 'invalid-failure'
row = {**{k:s[k] for k in ['id','file','test','pattern','check']},**observed,'status':status}
if observed['timeout']: row.update(cause=timeout_cause(s),control=control)
results.append(row)
result_file.write_text(json.dumps(results,indent=2)+'\n')
print(status,flush=True)
if status not in ('assertion-killed','killed-by-timeout'): raise SystemExit(1)
finally:
for name,data in originals.items(): (root/name).write_bytes(data)
if any((root/name).read_bytes()!=data for name,data in originals.items()): raise RuntimeError('restore failed')
summary = {'assertion_kills':sum(s['status']=='assertion-killed' for s in results),
'timeout_kills':sum(s['status']=='killed-by-timeout' for s in results),
'historical_timeout_kills':int(bool(args.adjudicate_timeout)),'selected':len(sites),
'run_id':run_id,'candidate':binding}
(packet/'mutant-resume-summary.json').write_text(json.dumps(summary,indent=2)+'\n')
print('MUTANT_SUMMARY',json.dumps(summary),flush=True)
@@ -0,0 +1,46 @@
--- agents/rocko/work/vikunja-502/run-mutants.baseline.py 2026-10-21 23:53:18.260554870 -0500
+++ agents/rocko/work/vikunja-502/run-mutants.py 2026-10-22 00:00:01.232405637 -0500
@@ -3,7 +3,7 @@
from pathlib import Path
import argparse, hashlib, json, os, re, signal, subprocess, sys, time
packet = Path(__file__).resolve().parent
-root = Path('/mnt/storage/scratch/rocko-r191-r3')
+root = Path('/mnt/storage/scratch/rocko-v502')
all_sites = json.loads((packet/'all-mutants.json').read_text())
parser = argparse.ArgumentParser()
parser.add_argument('ids', nargs='*')
@@ -104,11 +104,15 @@
'failing':re.findall(r'^\s*not ok \d+ - (.*)$',text,re.M),
'counts':{k:int(v) for k,v in re.findall(r'^# (tests|pass|fail|skipped|cancelled) (\d+)\s*$',text,re.M)}}
-def timeout_cause(site):
+def timeout_cause(site, control=None):
if site['id']=='SR08':
return ('runtimes.mjs:333 loops on now() <= deadline; stop-run.test.mjs:106-108 '
'gives the second host a fixed fake clock with a real sleep. Removing the join '
'branch enters the delivery loop without advancing that clock. Original log: TAP header only.')
+ if not control or control.get('passed') is not True:
+ return ('Mutated '+site['file']+' timed out after 90s; no passing same-file control '
+ 'exists in this invocation. Detection is not established. '
+ 'Detailed cause must be recorded before review.')
return ('Mutated '+site['file']+' timed out after 90s while the same whole test file '
'passed unmutated in this invocation. Detailed cause must be recorded before review.')
@@ -138,7 +142,7 @@
if not old or old['status']!='timeout' or old['test']!=args.control_file or old['failing']:
raise SystemExit('historical timeout does not match approved control')
if Path(old['log']).read_text().strip()!='TAP version 13': raise SystemExit('historical timeout log changed')
- adjudication = {**old,'status':'killed-by-timeout','cause':timeout_cause(site),'control':controls[args.control_file],
+ adjudication = {**old,'status':'killed-by-timeout','cause':timeout_cause(site, controls[args.control_file]),'control':controls[args.control_file],
'original_log_sha256':hashlib.sha256(Path(old['log']).read_bytes()).hexdigest()}
(packet/'SR08-timeout-adjudication.json').write_text(json.dumps(adjudication,indent=2)+'\n')
print('TIMEOUT_KILL SR08',adjudication['cause'],flush=True)
@@ -162,7 +166,7 @@
elif observed['failing'] and expected: status = 'assertion-killed'
else: status = 'invalid-failure'
row = {**{k:s[k] for k in ['id','file','test','pattern','check']},**observed,'status':status}
- if observed['timeout']: row.update(cause=timeout_cause(s),control=control)
+ if observed['timeout']: row.update(cause=timeout_cause(s, control),control=control)
results.append(row)
result_file.write_text(json.dumps(results,indent=2)+'\n')
print(status,flush=True)
@@ -0,0 +1 @@
c5bdc764d1cfe11752db2df412c96baa4f366236777df1755bb337e9a943ddea agents/rocko/work/vikunja-502/run-mutants.py
@@ -0,0 +1,5 @@
{
"scope": "owned502packet",
"binary_files": [],
"secret_pattern_matches": []
}
+1
View File
@@ -0,0 +1 @@
[]
+1
View File
@@ -0,0 +1 @@
/mnt/storage/scratch/tmp/gU6o