import json import subprocess from pathlib import Path import pytest from tests.dashboard_bundle import dashboard OUTBOX = Path(__file__).parents[1] / "frontend" / "issue-outbox.js" def run_node(script: str): result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) return json.loads(result.stdout) def test_issue_outbox_binds_items_and_only_flushes_for_the_matching_account(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const calls = []; let confirmedLogin = 'timmy'; const outbox = createIssueOutbox({{ storage, getOwnerLogin:() => confirmedLogin, createOperationId:() => 'bound-op', fetchJson:async url => {{ calls.push(url); return {{number:1}}; }}, }}); const queued = outbox.enqueue({{repository:'stackchain/api',title:'Bound',body:'Context'}}); confirmedLogin = 'alexander'; (async () => {{ const mismatch = await outbox.flush('alexander'); const unknown = await outbox.retry(queued.id, ''); const matched = await outbox.flush('timmy'); process.stdout.write(JSON.stringify({{queued,mismatch,unknown,matched,calls}})); }})(); """ output = run_node(script) assert output["queued"]["ownerLogin"] == "timmy" assert output["mismatch"]["blocked"] == 1 assert output["unknown"]["blocked"] == 1 assert len(output["matched"]["confirmed"]) == 1 assert len(output["calls"]) == 1 def test_issue_outbox_queues_multiple_planned_issues_with_stable_operation_ids(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{ getItem:key => values.has(key) ? values.get(key) : null, setItem:(key,value) => values.set(key,value), removeItem:key => values.delete(key), }}; let sequence = 0; const outbox = createIssueOutbox({{storage, getOwnerLogin:()=>'timmy', createOperationId:() => 'op-' + (++sequence), now:() => 1000 + sequence}}); outbox.enqueue({{repository:'stackchain/api',title:'First',body:'One',labelIds:[3]}}); outbox.enqueue({{repository:'stackchain/web',title:'Second',body:'Two',milestoneId:4,dueDate:'2026-08-09'}}); const reloaded = createIssueOutbox({{storage}}).list(); process.stdout.write(JSON.stringify({{items:reloaded, stored:JSON.parse(values.get('stackchain.issue-outbox.v1'))}})); """ output = run_node(script) assert [(item["repository"], item["title"]) for item in output["items"]] == [ ("stackchain/api", "First"), ("stackchain/web", "Second"), ] assert [item["operationId"] for item in output["items"]] == ["op-1", "op-2"] assert all(item["status"] == "queued" for item in output["items"]) assert output["stored"]["version"] == 3 def test_issue_outbox_preserves_initial_owner_and_disables_start_for_teammate(): script = f""" const createIssueOutbox=require({json.dumps(str(OUTBOX))}); const values=new Map();const calls=[]; const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v),removeItem:k=>values.delete(k)}}; const outbox=createIssueOutbox({{storage,getOwnerLogin:()=>'timmy',createOperationId:()=>'owner-outbox',fetchJson:async(url,options)=>{{calls.push(JSON.parse(options.body));return{{number:18,assignees:['alex']}};}}}}); const queued=outbox.enqueue({{repository:'o/r',title:'Delegate',assignee:'alex',assigneeName:'Alex',completionIntent:'create-and-start'}}); (async()=>{{await outbox.flush('timmy');process.stdout.write(JSON.stringify({{queued,calls}}));}})(); """ output = run_node(script) assert output["queued"]["assignee"] == "alex" assert output["queued"]["assigneeName"] == "Alex" assert "completionIntent" not in output["queued"] assert output["calls"] == [{ "title": "Delegate", "body": "", "label_ids": [], "assignee": "alex" }] def test_editing_queued_issue_can_return_initial_owner_to_self(): script = f""" const createIssueOutbox=require({json.dumps(str(OUTBOX))}); const values=new Map();const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; let id=0;const outbox=createIssueOutbox({{storage,getOwnerLogin:()=>'timmy',createOperationId:()=>String(++id)}}); const queued=outbox.enqueue({{repository:'o/r',title:'Delegate',assignee:'alex',assigneeName:'Alex'}}); const updated=outbox.update(queued.id,{{...queued,assignee:'',assigneeName:'',completionIntent:'create-and-start'}}); process.stdout.write(JSON.stringify(updated)); """ output = run_node(script) assert "assignee" not in output assert "assigneeName" not in output assert output["completionIntent"] == "create-and-start" assert output["operationId"] != "1" def test_create_and_start_estimate_survives_outbox_delivery_and_completion(): script = f""" const createIssueOutbox=require({json.dumps(str(OUTBOX))}); const values=new Map(); const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v),removeItem:k=>values.delete(k)}}; const outbox=createIssueOutbox({{ storage,getOwnerLogin:()=>'timmy',createOperationId:()=>'estimated-start', fetchJson:async()=>({{number:849,title:'Estimated work'}}), }}); const queued=outbox.enqueue({{repository:'stackchain/dashboard',title:'Estimated work', completionIntent:'create-and-start',estimateMinutes:45}}); (async()=>{{ const delivered=await outbox.flush('timmy'); process.stdout.write(JSON.stringify({{queued,completion:delivered.completions[0]}})); }})(); """ output = run_node(script) assert output["queued"]["estimateMinutes"] == 45 assert output["completion"]["estimateMinutes"] == 45 def test_editing_queued_start_estimate_rotates_the_delivery_operation(): script = f""" const createIssueOutbox=require({json.dumps(str(OUTBOX))});const values=new Map();let sequence=0; const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const outbox=createIssueOutbox({{storage,getOwnerLogin:()=>'timmy',createOperationId:()=> 'op-'+(++sequence)}}); const queued=outbox.enqueue({{repository:'o/r',title:'Sized',completionIntent:'create-and-start',estimateMinutes:30}}); const updated=outbox.update(queued.id,{{...queued,estimateMinutes:60}}); process.stdout.write(JSON.stringify({{queued,updated}})); """ output = run_node(script) assert output["updated"]["estimateMinutes"] == 60 assert output["updated"]["operationId"] != output["queued"]["operationId"] def test_issue_outbox_preserves_bounded_ordered_evidence_bundle(): script = f""" const createIssueOutbox=require({json.dumps(str(OUTBOX))}); const values=new Map();const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const image=name=>({{filename:name,contentType:'image/png',data:name,note:'Explain '+name}}); const outbox=createIssueOutbox({{storage,getOwnerLogin:()=>'timmy',createOperationId:()=>'bundle'}}); const queued=outbox.enqueue({{repository:'o/r',title:'Journey',attachments:['one','two','three','four','five'].map(image)}}); let limit='';try{{outbox.enqueue({{repository:'o/r',title:'Too many',attachments:['1','2','3','4','5','6'].map(image)}});}}catch(error){{limit=error.message;}} process.stdout.write(JSON.stringify({{queued,list:outbox.list(),limit}})); """ output = run_node(script) assert [item["filename"] for item in output["queued"]["attachments"]] == [ "one", "two", "three", "four", "five" ] assert output["list"][0]["attachments"] == output["queued"]["attachments"] assert [item["note"] for item in output["queued"]["attachments"]] == [ "Explain one", "Explain two", "Explain three", "Explain four", "Explain five" ] assert output["limit"] == "You can attach up to 5 screenshots." def test_foreground_issue_delivery_posts_the_same_captioned_evidence_bundle(): script = f""" const createIssueOutbox=require({json.dumps(str(OUTBOX))}); const values=new Map(),calls=[]; const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const fetchJson=async(url,options={{}})=>{{ let body=null; if(options.body instanceof FormData)body={{filename:options.body.get('file').name}}; else if(options.body)body=JSON.parse(options.body); calls.push({{url,key:options.headers?.['Idempotency-Key'],body}}); if(url.endsWith('/issues'))return{{number:827}}; if(url.endsWith('/attachments'))return{{markdown:'!['+body.filename+'](url/'+body.filename+')'}}; return{{id:9}}; }}; let id=0;const outbox=createIssueOutbox({{storage,fetchJson,getOwnerLogin:()=>'timmy',createOperationId:()=> 'op-'+(++id)}}); const image=(name,note)=>({{filename:name,contentType:'image/png',data:'eA==',note}}); outbox.enqueue({{repository:'o/r',title:'Journey',attachments:[image('one.png','First'),image('two.png','Tap *Submit*')]}}); (async()=>{{const result=await outbox.flush('timmy');process.stdout.write(JSON.stringify({{result,calls}}));}})(); """ output = run_node(script) assert output["result"]["confirmed"][0]["number"] == 827 uploads = [call for call in output["calls"] if call["url"].endswith("/attachments")] assert [call["body"]["filename"] for call in uploads] == ["one.png", "two.png"] comments = [call for call in output["calls"] if call["url"].endswith("/comments")] assert len(comments) == 1 assert comments[0]["body"]["body"] == ( "**Screenshot 1 — First**\n\n![one.png](url/one.png)\n\n" "**Screenshot 2 — Tap \\*Submit\\***\n\n![two.png](url/two.png)" ) def test_issue_outbox_replaces_evidence_bundle_when_editing_a_queued_issue(): script = f""" const createIssueOutbox=require({json.dumps(str(OUTBOX))});const values=new Map(); const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}};const image=name=>({{filename:name,contentType:'image/png',data:name}}); let sequence=0;const outbox=createIssueOutbox({{storage,getOwnerLogin:()=>'timmy',createOperationId:()=>String(++sequence)}}); const queued=outbox.enqueue({{repository:'o/r',title:'Journey',attachments:[image('old-one'),image('old-two')]}}); const updated=outbox.update(queued.id,{{...queued,attachments:[image('new-one'),image('new-two'),image('new-three')]}}); process.stdout.write(JSON.stringify(updated)); """ output = run_node(script) assert [item["filename"] for item in output["attachments"]] == ["new-one", "new-two", "new-three"] assert output["operationId"] != output["id"] def test_issue_outbox_persists_a_validated_screenshot_with_the_capture(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=> 'timmy', createOperationId:()=> 'capture-image-1', }}); outbox.enqueue({{ repository:'stackchain/dashboard',title:'Layout breaks',body:'At 320px', attachment:{{filename:'phone.webp',contentType:'image/webp',data:'UklGRg=='}}, }}); process.stdout.write(JSON.stringify(createIssueOutbox({{storage}}).list()[0])); """ output = run_node(script) assert output["attachment"] == { "filename": "phone.webp", "contentType": "image/webp", "data": "UklGRg==", } def test_issue_outbox_hydrates_durable_evidence_bundle_without_copying_blobs_to_localstorage(): script = f""" const createIssueOutbox=require({json.dumps(str(OUTBOX))}); const values=new Map();const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const image=name=>({{filename:name,contentType:'image/png',blob:new Blob([name],{{type:'image/png'}})}}); let durable=null; const outbox=createIssueOutbox({{storage,getOwnerLogin:()=>'timmy',createOperationId:()=>'bundle',backgroundSync:{{ reconcile:async items=>{{durable=items[0];}},requestSync:async()=>{{}},get:async()=>durable, }}}}); (async()=>{{const admitted=await outbox.enqueueDurably({{repository:'o/r',title:'Journey',attachments:['one','two'].map(image)}}); const raw=values.get('stackchain.issue-outbox.v1');const hydrated=await outbox.hydrateForEdit(admitted.item.id); process.stdout.write(JSON.stringify({{raw,names:hydrated.attachments.map(x=>x.filename),sizes:hydrated.attachments.map(x=>x.blob.size)}}));}})(); """ output = run_node(script) assert '"stored":true' in output["raw"] assert '"blob"' not in output["raw"] assert output["names"] == ["one", "two"] assert output["sizes"] == [3, 3] def test_issue_outbox_hydrates_a_metadata_only_screenshot_for_edit_without_copying_bytes_to_localstorage(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; values.set('stackchain.issue-outbox.v1', JSON.stringify({{version:3,items:[{{ id:'capture-1',operationId:'capture-1',repository:'o/r',title:'Before',body:'', ownerLogin:'timmy',status:'queued',attachment:{{filename:'phone.png',contentType:'image/png',stored:true}}, }}]}})); const gets=[]; const outbox=createIssueOutbox({{storage,backgroundSync:{{ get:async id=>{{gets.push(id);return {{id,operationId:'capture-1',attachment:{{filename:'phone.png',contentType:'image/png',data:'durable-image-bytes'}}}};}}, }}}}); (async()=>{{ const hydrated=await outbox.hydrateForEdit('capture-1'); process.stdout.write(JSON.stringify({{hydrated,gets,stored:values.get('stackchain.issue-outbox.v1')}})); }})(); """ output = run_node(script) assert output["hydrated"]["attachment"]["data"] == "durable-image-bytes" assert output["gets"] == ["capture-1"] assert "durable-image-bytes" not in output["stored"] def test_issue_outbox_persists_create_and_start_intent_and_returns_it_with_confirmation(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=> 'timmy', createOperationId:()=> 'create-start-1', fetchJson:async () => ({{repository:'stackchain/dashboard',number:389,title:'Resume me'}}), }}); outbox.enqueue({{ repository:'stackchain/dashboard',title:'Resume me',body:'',completionIntent:'create-and-start' }}); const reloaded = createIssueOutbox({{ storage, fetchJson:async () => ({{repository:'stackchain/dashboard',number:389,title:'Resume me'}}) }}); reloaded.flush('timmy').then(result => process.stdout.write(JSON.stringify({{ persisted:JSON.parse(values.get('stackchain.issue-outbox.v1')), result, }}))); """ output = run_node(script) assert output["persisted"]["version"] == 3 completion = output["result"]["completions"][0] assert completion["intent"] == "create-and-start" assert completion["ownerLogin"] == "timmy" assert completion["operationId"] == "create-start-1" assert completion["issue"]["number"] == 389 def test_editing_a_queued_issue_updates_its_completion_intent_without_rekeying_delivery(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values=new Map();let sequence=0; const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const outbox=createIssueOutbox({{ storage,getOwnerLogin:()=>'timmy',createOperationId:()=> 'intent-' + (++sequence) }}); const queued=outbox.enqueue({{repository:'o/r',title:'Work',body:''}}); const started=outbox.update(queued.id,{{...queued,completionIntent:'create-and-start'}}); const ordinary=outbox.update(queued.id,{{...started,completionIntent:undefined}}); process.stdout.write(JSON.stringify({{started,ordinary}})); """ output = run_node(script) assert output["started"]["completionIntent"] == "create-and-start" assert "completionIntent" not in output["ordinary"] assert output["started"]["operationId"] == output["ordinary"]["operationId"] == "intent-1" def test_issue_outbox_flushes_sequentially_and_keeps_transient_failures_queued(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v),removeItem:k=>values.delete(k)}}; let sequence = 0; const calls = []; const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=>'timmy', createOperationId:() => 'stable-' + (++sequence), fetchJson:async (url, options) => {{ calls.push({{url, key:options.headers['Idempotency-Key']}}); if (calls.length === 2) {{ const error = new Error('upstream unavailable'); error.status = 503; throw error; }} return {{repository:'stackchain/api',number:41,title:'First'}}; }}, }}); outbox.enqueue({{repository:'stackchain/api',title:'First',body:'One'}}); outbox.enqueue({{repository:'stackchain/web',title:'Second',body:'Two'}}); outbox.flush('timmy').then(result => process.stdout.write(JSON.stringify({{result,calls,remaining:outbox.list()}}))); """ output = run_node(script) assert output["result"]["confirmed"][0]["number"] == 41 assert output["calls"] == [ {"url": "api/v1/repos/stackchain/api/issues", "key": "stable-1"}, {"url": "api/v1/repos/stackchain/web/issues", "key": "stable-2"}, ] assert len(output["remaining"]) == 1 assert output["remaining"][0]["title"] == "Second" assert output["remaining"][0]["status"] == "queued" def test_issue_outbox_exposes_sending_then_preserves_transient_attempt_details(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; let release; const gate = new Promise(resolve => release = resolve); const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=>'timmy', createOperationId:()=>'attempt', now:()=>888, fetchJson:async()=>{{await gate;const error=new Error('Gateway timeout');error.status=503;throw error;}}, }}); outbox.enqueue({{repository:'o/r',title:'Wait',body:'Context'}}); const pending=outbox.flush('timmy'); const sending=outbox.list()[0]; release(); pending.then(()=>process.stdout.write(JSON.stringify({{sending,after:outbox.list()[0]}}))); """ output = run_node(script) assert output["sending"]["status"] == "sending" assert output["after"]["status"] == "queued" assert output["after"]["lastAttemptAt"] == 888 assert output["after"]["lastAttemptError"] == "Gateway timeout" def test_foreground_attachment_retry_does_not_create_a_second_issue(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values=new Map();const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const calls=[];let uploadAttempts=0; const outbox=createIssueOutbox({{ storage,getOwnerLogin:()=>'timmy',createOperationId:()=>'image-op', fetchJson:async(url,options={{}})=>{{ const form=options.body instanceof FormData ? options.body : null; const file=form?.get('file'); calls.push({{url,key:options.headers?.['Idempotency-Key'],multipart:Boolean(form), filename:file?.name,text:file?await file.text():null}}); if(url.endsWith('/issues'))return{{repository:'o/r',number:7,title:'Visual bug'}}; if(url.endsWith('/attachments') && uploadAttempts++ === 0){{const error=new Error('offline');error.status=503;throw error;}} if(url.endsWith('/attachments'))return{{markdown:'![screen.png]()'}}; return{{id:8}}; }}, }}); const queued=outbox.enqueue({{repository:'o/r',title:'Visual bug',attachment:{{filename:'screen.png',contentType:'image/png',data:'b2ZmbGluZQ=='}}}}); (async()=>{{await outbox.flush('timmy');const partial=outbox.list()[0];const result=await outbox.retry(queued.id,'timmy');process.stdout.write(JSON.stringify({{calls,partial,result,remaining:outbox.list()}}));}})(); """ output = run_node(script) assert output["partial"]["deliveredIssue"]["number"] == 7 assert [call["url"] for call in output["calls"]].count("api/v1/repos/o/r/issues") == 1 uploads = [call for call in output["calls"] if call["url"].endswith("/attachments")] assert uploads[-1]["multipart"] is True assert uploads[-1]["filename"] == "screen.png" assert uploads[-1]["text"] == "offline" assert output["calls"][-1]["url"] == "api/v1/repos/o/r/issues/7/comments" assert output["result"]["confirmed"][0]["number"] == 7 assert output["remaining"] == [] def test_foreground_blocker_retry_checkpoints_each_relationship_without_recreating_issue(): script = f""" const createIssueOutbox=require({json.dumps(str(OUTBOX))}); const values=new Map();const calls=[];let secondAttempts=0; const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const outbox=createIssueOutbox({{ storage,getOwnerLogin:()=>'timmy',createOperationId:()=>'blocked-op', fetchJson:async(url,options={{}})=>{{ const body=options.body ? JSON.parse(options.body) : null; calls.push({{url,key:options.headers?.['Idempotency-Key'],body}}); if(url.endsWith('/issues'))return{{repository:'o/r',number:42,title:'Blocked work'}}; if(body?.number===8 && secondAttempts++===0){{const error=new Error('offline');error.status=503;throw error;}} return{{number:42,dependencies_available:true,dependencies:[body]}}; }}, }}); const blockers=[ {{repository:'o/api',number:7,title:'API ready'}}, {{repository:'o/web',number:8,title:'Web ready'}}, ]; const queued=outbox.enqueue({{repository:'o/r',title:'Blocked work',body:'',blockers}}); (async()=>{{ await outbox.flush('timmy');const partial=outbox.list()[0]; const result=await outbox.retry(queued.id,'timmy'); process.stdout.write(JSON.stringify({{queued,partial,result,calls,remaining:outbox.list()}})); }})(); """ output = run_node(script) assert output["queued"]["blockers"] == [ {"repository": "o/api", "number": 7, "title": "API ready"}, {"repository": "o/web", "number": 8, "title": "Web ready"}, ] assert output["partial"]["deliveredIssue"]["number"] == 42 assert output["partial"]["deliveredBlockers"] == 1 assert [call["url"] for call in output["calls"]].count("api/v1/repos/o/r/issues") == 1 blocker_calls = [call for call in output["calls"] if call["url"].endswith("/blockers")] assert [call["body"]["number"] for call in blocker_calls] == [7, 8, 8] assert blocker_calls[0]["key"] == "blocked-op:blocker-0" assert blocker_calls[-1]["key"] == "blocked-op:blocker-1" assert output["result"]["confirmed"][0]["number"] == 42 assert output["remaining"] == [] def test_replacing_a_partial_capture_screenshot_keeps_issue_and_restarts_upload_stage(): script = f""" const createIssueOutbox=require({json.dumps(str(OUTBOX))}); const values=new Map();const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}};let sequence=0; const outbox=createIssueOutbox({{storage,getOwnerLogin:()=>'timmy',createOperationId:()=> 'op-'+(++sequence)}}); const queued=outbox.enqueue({{repository:'o/r',title:'Visual',attachment:{{filename:'old.png',contentType:'image/png',data:'old'}}}}); values.set('stackchain.issue-outbox.v1',JSON.stringify({{version:3,items:[{{...queued,deliveredIssue:{{repository:'o/r',number:7}},attachmentMarkdown:'![old](url)'}}]}})); const updated=outbox.update(queued.id,{{...queued,attachment:{{filename:'new.png',contentType:'image/png',data:'new'}}}}); process.stdout.write(JSON.stringify(updated)); """ output = run_node(script) assert output["deliveredIssue"]["number"] == 7 assert output["attachment"]["filename"] == "new.png" assert "attachmentMarkdown" not in output def test_issue_outbox_preserves_permanent_failures_for_edit_and_retry(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v),removeItem:k=>values.delete(k)}}; let invalid = true; let operationSequence = 0; const calls = []; const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=>'timmy', createOperationId:() => 'edit-' + (++operationSequence), fetchJson:async (_url, options) => {{ calls.push({{key:options.headers['Idempotency-Key'],body:JSON.parse(options.body)}}); if (invalid) {{ const error = new Error('Title is invalid'); error.status = 422; throw error; }} return {{repository:'stackchain/api',number:42,title:'Fixed title'}}; }}, }}); const queued = outbox.enqueue({{repository:'stackchain/api',title:'Bad',body:'Context'}}); outbox.flush('timmy').then(async () => {{ const attention = outbox.list()[0]; outbox.update(queued.id, {{...attention,title:'Fixed title'}}); invalid = false; const result = await outbox.retry(queued.id, 'timmy'); process.stdout.write(JSON.stringify({{attention,result,calls,remaining:outbox.list()}})); }}); """ output = run_node(script) assert output["attention"]["status"] == "attention" assert output["attention"]["error"] == "Title is invalid" assert [call["key"] for call in output["calls"]] == ["edit-1", "edit-2"] assert output["calls"][1]["body"]["title"] == "Fixed title" assert output["result"]["confirmed"][0]["number"] == 42 assert output["remaining"] == [] def test_issue_outbox_rekeys_only_after_user_retries_uncertain_delivery(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); let sequence=0; const calls=[]; const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=>'timmy', createOperationId:()=> 'operation-' + (++sequence), fetchJson:async (_url, options) => {{ const key=options.headers['Idempotency-Key']; calls.push(key); if (key === 'operation-1') {{ const e=new Error('Verify it was not posted before retrying.'); e.status=422; e.code='delivery_uncertain'; throw e; }} return {{number:319}}; }}, }}); const item=outbox.enqueue({{repository:'o/r',title:'Possibly sent',body:'Check first'}}); (async()=>{{ await outbox.flush('timmy'); const attention=outbox.list()[0]; await outbox.retry(item.id,'timmy'); process.stdout.write(JSON.stringify({{attention,calls,remaining:outbox.list()}})); }})(); """ output = run_node(script) assert output["attention"]["status"] == "attention" assert output["attention"]["deliveryState"] == "uncertain" assert output["calls"] == ["operation-1", "operation-2"] assert output["remaining"] == [] def test_issue_outbox_rotates_operation_id_only_when_delivery_payload_changes(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; let sequence = 0; const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=>'timmy', createOperationId:() => 'payload-' + (++sequence), }}); const queued = outbox.enqueue({{ repository:'stackchain/api',title:'Plan',body:'Context',labelIds:[1],milestoneId:2,dueDate:'2026-08-09' }}); const unchanged = outbox.update(queued.id, {{...queued}}); const fields = [ ['repository', 'stackchain/web'], ['title', 'Revised'], ['body', 'More context'], ['labelIds', [1, 3]], ['milestoneId', 4], ['dueDate', '2026-08-10'], ]; const edits = []; for (const [field, value] of fields) {{ const current = outbox.list()[0]; edits.push(outbox.update(queued.id, {{...current, [field]:value}})); }} process.stdout.write(JSON.stringify({{queued,unchanged,edits,final:outbox.list()[0]}})); """ output = run_node(script) assert output["unchanged"]["operationId"] == output["queued"]["operationId"] == "payload-1" assert [item["operationId"] for item in output["edits"]] == [ "payload-2", "payload-3", "payload-4", "payload-5", "payload-6", "payload-7" ] assert output["final"]["milestoneId"] == 4 assert output["final"]["dueDate"] == "2026-08-10" def test_issue_outbox_is_single_flight_when_reconnect_and_send_now_overlap(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v),removeItem:k=>values.delete(k)}}; let calls = 0; let release; const gate = new Promise(resolve => {{ release = resolve; }}); const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=>'timmy', createOperationId:() => 'single-flight', fetchJson:async () => {{ calls += 1; await gate; return {{repository:'stackchain/api',number:43,title:'Once'}}; }}, }}); const queued = outbox.enqueue({{repository:'stackchain/api',title:'Once',body:'Context'}}); const reconnect = outbox.flush('timmy'); const sendNow = outbox.retry(queued.id, 'timmy'); release(); Promise.all([reconnect,sendNow]).then(results => process.stdout.write(JSON.stringify({{calls,results,remaining:outbox.list()}}))); """ output = run_node(script) assert output["calls"] == 1 assert output["remaining"] == [] assert sum(len(result["confirmed"]) for result in output["results"]) >= 1 def test_issue_outbox_mirrors_queue_and_registers_background_sync(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const state = {{snapshots:[],syncs:0}}; const backgroundSync = {{ reconcile: async items => state.snapshots.push(items.map(item => ({{...item}}))), requestSync: async () => {{ state.syncs += 1; }}, }}; const outbox = createIssueOutbox({{ storage, backgroundSync, getOwnerLogin:()=> 'timmy', createOperationId:()=> 'background-1', }}); outbox.enqueue({{repository:'stackchain/api',title:'Close the app',body:'Still deliver'}}); setTimeout(() => process.stdout.write(JSON.stringify(state)), 0); """ output = run_node(script) assert output["snapshots"][0][0]["operationId"] == "background-1" assert output["snapshots"][0][0]["ownerLogin"] == "timmy" assert output["syncs"] == 1 def test_issue_outbox_confirms_durable_admission_only_after_mirror_and_sync(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; let releaseMirror; let releaseSync; const mirrorGate = new Promise(resolve => releaseMirror = resolve); const syncGate = new Promise(resolve => releaseSync = resolve); const events = []; const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=> 'timmy', createOperationId:()=> 'durable-1', backgroundSync: {{ reconcile: async items => {{ events.push('mirror-start'); await mirrorGate; events.push('mirror-committed'); }}, requestSync: async () => {{ events.push('sync-start'); await syncGate; events.push('sync-registered'); }}, }}, }}); const admission = outbox.enqueueDurably({{repository:'stackchain/api',title:'Keep this',body:'Draft'}}) .then(result => {{ events.push('confirmed'); return result; }}); Promise.resolve().then(async () => {{ const beforeMirror = events.slice(); releaseMirror(); await new Promise(resolve => setTimeout(resolve, 0)); const beforeSync = events.slice(); releaseSync(); const result = await admission; process.stdout.write(JSON.stringify({{beforeMirror,beforeSync,events,result,items:outbox.list()}})); }}); """ output = run_node(script) assert output["beforeMirror"] == ["mirror-start"] assert output["beforeSync"] == ["mirror-start", "mirror-committed", "sync-start"] assert output["events"][-1] == "confirmed" assert output["result"]["background"] is True assert output["result"]["item"]["id"] == "durable-1" assert output["items"][0]["title"] == "Keep this" def test_durable_screenshot_is_mirrored_as_blob_before_a_payload_free_local_index_is_committed(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const events = []; const snapshots = []; const storage = {{ getItem:key => values.get(key) || null, setItem:(key,value) => {{ if (value.includes('binary-screenshot-bytes')) throw new Error('screenshot leaked into localStorage'); events.push('local-index'); values.set(key,value); }}, }}; const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=> 'timmy', createOperationId:()=> 'quota-safe-1', backgroundSync: {{ reconcile: async items => {{ events.push('indexeddb'); snapshots.push(items); }}, requestSync: async () => {{ events.push('sync'); }}, }}, }}); (async () => {{ const blob=new Blob(['binary-screenshot-bytes'],{{type:'image/webp'}}); const result = await outbox.enqueueDurably({{ repository:'stackchain/dashboard', title:'Mobile layout', attachment:{{filename:'phone.webp',contentType:'image/webp',blob}}, }}); const durable=snapshots[0][0].attachment; process.stdout.write(JSON.stringify({{ events, mirrored:{{filename:durable.filename,contentType:durable.contentType, isBlob:durable.blob instanceof Blob,size:durable.blob?.size,text:await durable.blob?.text()}}, local:outbox.list()[0], result:result.item, raw:values.get('stackchain.issue-outbox.v1'), }})); }})(); """ output = run_node(script) assert output["events"] == ["indexeddb", "local-index", "sync"] assert output["mirrored"] == { "filename": "phone.webp", "contentType": "image/webp", "isBlob": True, "size": 23, "text": "binary-screenshot-bytes", } assert output["local"]["attachment"] == { "filename": "phone.webp", "contentType": "image/webp", "stored": True, } assert "binary-screenshot-bytes" not in output["raw"] assert output["result"]["attachment"] == output["local"]["attachment"] def test_replacing_a_durable_screenshot_updates_indexeddb_before_the_local_reference(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const events = []; const snapshots = []; const storage = {{ getItem:key => values.get(key) || null, setItem:(key,value) => {{ if (value.includes('new-image-bytes')) throw new Error('replacement leaked into localStorage'); events.push('local-index'); values.set(key,value); }}, }}; const backgroundSync = {{ reconcile:async items => {{ events.push('indexeddb'); snapshots.push(items); }}, requestSync:async () => {{ events.push('sync'); }}, }}; const outbox = createIssueOutbox({{ storage, backgroundSync, getOwnerLogin:()=> 'timmy', createOperationId:(() => {{ let sequence=0; return () => 'replace-' + (++sequence); }})(), }}); (async () => {{ const admitted = await outbox.enqueueDurably({{repository:'o/r',title:'Visual'}}); events.length = 0; const result = await outbox.updateDurably(admitted.item.id, {{ ...admitted.item, attachment:{{filename:'new.png',contentType:'image/png',data:'new-image-bytes'}}, }}); process.stdout.write(JSON.stringify({{ events, mirrored:snapshots.at(-1)[0], local:outbox.list()[0], result:result.item, }})); }})(); """ output = run_node(script) assert output["events"] == ["indexeddb", "local-index", "sync"] assert output["mirrored"]["attachment"]["data"] == "new-image-bytes" assert output["local"]["attachment"]["stored"] is True assert "data" not in output["local"]["attachment"] assert output["result"] == output["local"] def test_next_durable_admission_migrates_legacy_screenshot_payloads_out_of_localstorage(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map([['stackchain.issue-outbox.v1', JSON.stringify({{version:3,items:[{{ id:'legacy',operationId:'legacy',repository:'o/r',title:'Queued before upgrade',body:'', labelIds:[],ownerLogin:'timmy',status:'queued',queuedAt:1, attachment:{{filename:'old.png',contentType:'image/png',data:'legacy-image-bytes'}}, }}]}})]]); const mirrored = []; const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=> 'timmy', createOperationId:()=> 'new-item', backgroundSync:{{reconcile:async items=>mirrored.push(items),requestSync:async()=>{{}}}}, }}); (async()=>{{ await outbox.enqueueDurably({{repository:'o/r',title:'New queue item'}}); process.stdout.write(JSON.stringify({{ mirrored:mirrored[0],local:outbox.list(),raw:values.get('stackchain.issue-outbox.v1'), }})); }})(); """ output = run_node(script) assert output["mirrored"][0]["attachment"]["data"] == "legacy-image-bytes" assert output["local"][0]["attachment"]["stored"] is True assert "data" not in output["local"][0]["attachment"] assert "legacy-image-bytes" not in output["raw"] def test_issue_outbox_reports_degraded_admission_without_losing_foreground_item(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=> 'timmy', createOperationId:()=> 'foreground-1', backgroundSync: {{ reconcile: async () => {{}}, requestSync: async () => {{ throw new Error('Background Sync unavailable'); }}, }}, }}); outbox.enqueueDurably({{repository:'stackchain/api',title:'Retain me',body:'Draft'}}).then(result => process.stdout.write(JSON.stringify({{ background:result.background, durability:result.durability, error:result.error.message, item:result.item, persisted:outbox.list() }})) ); """ output = run_node(script) assert output["background"] is False assert output["durability"] == "foreground-only" assert output["error"] == "Background Sync unavailable" assert output["persisted"] == [output["item"]] def test_foreground_delivery_uses_same_atomic_background_claim(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const state = {{backgroundCalls:0,directCalls:0}}; const backgroundSync = {{ reconcile: async () => {{}}, requestSync: async () => {{}}, send: async (item, login) => {{ state.backgroundCalls += 1; return {{issue:{{number:9}}, claimed:item.id + ':' + login}}; }}, }}; const outbox = createIssueOutbox({{ storage, backgroundSync, getOwnerLogin:()=> 'timmy', createOperationId:()=> 'shared-claim', fetchJson:async () => {{ state.directCalls += 1; return {{number:10}}; }}, }}); outbox.enqueue({{repository:'o/r',title:'One owner'}}); outbox.flush('timmy').then(result => process.stdout.write(JSON.stringify({{state,result,remaining:outbox.list()}}))); """ output = run_node(script) assert output["state"] == {"backgroundCalls": 1, "directCalls": 0} assert output["result"]["confirmed"][0]["number"] == 9 assert output["remaining"] == [] def test_foreground_background_delivery_surfaces_permanent_failure_immediately(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values=new Map();const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const error=new Error('Title is invalid');error.status=422; const backgroundSync={{reconcile:async()=>{{}},requestSync:async()=>{{}},send:async()=>({{attention:true,error}})}}; const outbox=createIssueOutbox({{storage,backgroundSync,getOwnerLogin:()=>'timmy',createOperationId:()=>'invalid'}}); outbox.enqueue({{repository:'o/r',title:'Bad'}}); outbox.flush('timmy').then(result=>process.stdout.write(JSON.stringify({{result,items:outbox.list()}}))); """ output = run_node(script) assert output["items"][0]["status"] == "attention" assert output["items"][0]["error"] == "Title is invalid" assert output["result"]["confirmed"] == [] def test_page_reconciles_worker_success_and_attention_into_visible_outbox(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values=new Map(); const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; values.set('stackchain.issue-outbox.v1',JSON.stringify({{version:2,items:[ {{id:'sent',operationId:'sent',ownerLogin:'timmy',status:'queued',title:'Done'}}, {{id:'bad',operationId:'bad',ownerLogin:'timmy',status:'queued',title:'Fix me'}}, ]}})); const outbox=createIssueOutbox({{storage}}); outbox.reconcileBackground([ {{id:'sent',status:'sent'}}, {{id:'bad',status:'attention',error:'Title is invalid'}}, ]); process.stdout.write(JSON.stringify(outbox.list())); """ output = run_node(script) assert output == [ { "id": "bad", "operationId": "bad", "ownerLogin": "timmy", "status": "attention", "title": "Fix me", "error": "Title is invalid", } ] def test_page_reconciliation_keeps_confirmed_attachment_delivery_stages(): script = f""" const createIssueOutbox=require({json.dumps(str(OUTBOX))}); const values=new Map();const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const outbox=createIssueOutbox({{storage,getOwnerLogin:()=>'timmy',createOperationId:()=>'staged'}}); outbox.enqueue({{repository:'o/r',title:'Visual',attachment:{{filename:'a.png',contentType:'image/png',data:'abc'}}}}); outbox.reconcileBackground([{{id:'staged',status:'queued',deliveredIssue:{{repository:'o/r',number:7}},attachmentMarkdown:'![a](url)'}}]); process.stdout.write(JSON.stringify(outbox.list()[0])); """ output = run_node(script) assert output["deliveredIssue"]["number"] == 7 assert output["attachmentMarkdown"] == "![a](url)" def test_page_preserves_background_create_and_start_until_the_matching_account_continues_it(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values=new Map(); const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; values.set('stackchain.issue-outbox.v1',JSON.stringify({{version:3,items:[{{ id:'resume',operationId:'resume',ownerLogin:'timmy',status:'queued', repository:'stackchain/dashboard',title:'Resume',body:'',completionIntent:'create-and-start' }}]}})); const outbox=createIssueOutbox({{storage}}); outbox.reconcileBackground([{{ id:'resume',status:'sent',ownerLogin:'timmy',completionIntent:'create-and-start', deliveredIssue:{{repository:'stackchain/dashboard',number:389,title:'Resume'}} }}]); const blocked=outbox.pendingCompletions('alexander'); const pending=outbox.pendingCompletions('timmy'); const retained=outbox.list(); outbox.completeIntent('resume','timmy'); process.stdout.write(JSON.stringify({{blocked,pending,retained,remaining:outbox.list()}})); """ output = run_node(script) assert output["blocked"] == [] assert output["pending"] == [{ "id": "resume", "intent": "create-and-start", "ownerLogin": "timmy", "operationId": "resume", "issue": {"repository": "stackchain/dashboard", "number": 389, "title": "Resume"}, }] assert output["retained"][0]["status"] == "completion" assert output["remaining"] == [] @pytest.mark.anyio async def test_mobile_dashboard_queues_offline_captures_and_exposes_outbox_actions(): html = await dashboard() assert '' in html assert "const issueOutbox = createIssueOutbox({" in html assert "await issueOutbox.enqueueDurably(durableDraft)" in html assert "Saving for background delivery…" in html assert "Saved for next launch; background delivery unavailable." in html assert "throw new Error('Background Sync unavailable')" in html assert "issueOutbox.retry(queued.id, activeFlushLogin)" in html assert "navigator.onLine" in html assert "activeFlushLogin = contextIdentityFresh ?" in html assert "flushIssueOutbox();" in html assert 'class="draft-send"' in html assert 'class="draft-edit"' in html assert 'Queued for sync' in html assert 'Needs attention' in html assert "issueOutbox.discard(item.outbox_id)" in html assert '.draft-actions { display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr));' in html def test_issue_outbox_reuses_the_original_operation_when_a_source_draft_is_retried(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; let sequence = 0; const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=> 'timmy', createOperationId:()=> 'draft-op-' + (++sequence), }}); const first = outbox.enqueue({{repository:'o/r',title:'Draft work',body:'Context',sourceCaptureId:'capture-7'}}); const retried = outbox.enqueue({{repository:'o/r',title:'Draft work',body:'Context',sourceCaptureId:'capture-7'}}); process.stdout.write(JSON.stringify({{first,retried,items:outbox.list(),sequence}})); """ output = run_node(script) assert output["sequence"] == 1 assert len(output["items"]) == 1 assert output["first"]["id"] == output["retried"]["id"] == "draft-op-1" assert output["items"][0]["operationId"] == "draft-op-1" assert output["items"][0]["sourceCaptureId"] == "capture-7" def test_durable_source_draft_retry_reconciles_without_allocating_another_operation(): script = f""" const createIssueOutbox = require({json.dumps(str(OUTBOX))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; let sequence = 0; const reconciliations = []; let syncRequests = 0; const outbox = createIssueOutbox({{ storage, getOwnerLogin:()=> 'timmy', createOperationId:()=> 'durable-' + (++sequence), backgroundSync:{{ reconcile:async items=>reconciliations.push(items.map(item=>item.operationId)), requestSync:async()=>{{ syncRequests += 1; }}, }}, }}); (async()=>{{ const draft={{repository:'o/r',title:'Draft work',body:'Context',sourceCaptureId:'capture-8'}}; const first=await outbox.enqueueDurably(draft); const retried=await outbox.enqueueDurably(draft); process.stdout.write(JSON.stringify({{first,retried,reconciliations,syncRequests,items:outbox.list()}})); }})(); """ output = run_node(script) assert "reused" not in output["first"] assert output["retried"]["reused"] is True assert output["reconciliations"] == [["durable-1"], ["durable-1"]] assert output["syncRequests"] == 2 assert len(output["items"]) == 1