fix: rotate idempotency keys after offline issue edits (#283)
CI / lint (pull_request) Successful in 29s
CI / build-frontend (pull_request) Successful in 5s

This commit is contained in:
timmy committed 2026-08-08 09:00:21 +00:00
1 parent 997cb7dcd7
commit 3b85b4f35d
4 files changed
+57 -8

No files matched your search

+37 -2
View File
@@ -110,9 +110,10 @@ 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:() => 'stable-edit',
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; }}
@@ -132,12 +133,46 @@ outbox.flush('timmy').then(async () => {{
assert output["attention"]["status"] == "attention"
assert output["attention"]["error"] == "Title is invalid"
assert [call["key"] for call in output["calls"]] == ["stable-edit", "stable-edit"]
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_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))});
+2 -2
View File
@@ -91,10 +91,10 @@ async function dispatchNotificationClick(route) {{
return json.loads(completed.stdout)
def test_issue_handoff_ships_in_a_new_shell_cache():
def test_edited_issue_retry_ships_in_a_new_shell_cache():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v21" in source
assert "stackchain-dashboard-shell-v22" in source
assert "BASE + 'static/today-work.js'" in source