{"schema_version":"evidence-package@2.3","spec_version":"devtask.execution@1.0","bundle_id":"devtask-execution-dt-1790932263861-2eel-2-1790940074459","capability":"devtask.execution@1.0","run_id":"dt-1790932263861-2eel:2","claim_ladder":"L1","executed_at":"2026-10-02T11:21:14.490Z","trace_events":["devtask_contract_bound:{\"task_id\":\"dt-1790932263861-2eel\",\"attempt_number\":2,\"contract_hash\":\"1588a8e1dad042549006de90678d34831845909d1932f18b1aceeed9440aecc9\",\"contract_schema_version\":\"work-contract@1.0\",\"risk_class\":\"R2\",\"protect_existing_tests\":true,\"test_protection_disclosure\":\"disclosed\"}","devtask_attempt_started:{\"task_id\":\"dt-1790932263861-2eel\",\"attempt_number\":2,\"started_at\":\"2026-10-02T09:13:07.879Z\"}","devtask_execution_context:{\"task_id\":\"dt-1790932263861-2eel\",\"attempt_number\":2,\"execution_reference\":\"dev_task_attempts:19\",\"sandbox_reference\":\"sandbox-attestation:sha256:39bd0b9a4d2ef1f51fa541d0d2b5d5c796b22886aa238976eab0aa6361fd5b01\",\"executor\":\"deepseek\",\"harness_id\":\"agent-loop-server\",\"adapter_version\":\"repo-contribution-pipeline/deepseek@1.0.0\",\"adapter_version_sha256\":\"c36e789ec4f969715737cecd0c76ea879d3c955623e0a61e1fad3439d992e63b\"}","devtask_negative_claim:{\"task_id\":\"dt-1790932263861-2eel\",\"attempt_number\":2,\"claim\":\"no_capability_added_mid_run\",\"verified_by\":\"run_bindings:bound_at_vs_run_start\",\"strength\":\"observed\",\"result\":true,\"evidence_ref\":{\"artifact\":\"runtime_trace\",\"id\":\"trace_events:without_negative_claims\",\"digest\":\"sha256:0fab6f0196134cf5c2674653f67ac11201638e0ca85cbd570d8966a6393962d8\"}}","devtask_negative_claim:{\"task_id\":\"dt-1790932263861-2eel\",\"attempt_number\":2,\"claim\":\"no_network_outside_scope\",\"verified_by\":\"sandbox_policy:bubblewrap_unshare_net_fail_closed\",\"strength\":\"policy_derived\",\"result\":true,\"evidence_ref\":{\"artifact\":\"attestation\",\"id\":\"controller_evidence.sandbox_attestation\",\"digest\":\"sha256:9cdcece5f30a001f433dd687b9f1697cb73089c4b5ab6a257be6a6ee8b8fd7a4\"}}","devtask_negative_claim:{\"task_id\":\"dt-1790932263861-2eel\",\"attempt_number\":2,\"claim\":\"no_secret_access\",\"verified_by\":\"sandbox_policy:bubblewrap_clearenv\",\"strength\":\"policy_derived\",\"result\":true,\"evidence_ref\":{\"artifact\":\"runtime_trace\",\"id\":\"trace_events:without_negative_claims\",\"digest\":\"sha256:0fab6f0196134cf5c2674653f67ac11201638e0ca85cbd570d8966a6393962d8\"}}","devtask_negative_claim:{\"task_id\":\"dt-1790932263861-2eel\",\"attempt_number\":2,\"claim\":\"no_write_outside_allowlist\",\"verified_by\":\"policy_kernel_process:file_observation\",\"strength\":\"observed\",\"result\":true,\"evidence_ref\":{\"artifact\":\"runtime_trace\",\"id\":\"trace_events:without_negative_claims\",\"digest\":\"sha256:0fab6f0196134cf5c2674653f67ac11201638e0ca85cbd570d8966a6393962d8\"}}","devtask_negative_claim:{\"task_id\":\"dt-1790932263861-2eel\",\"attempt_number\":2,\"claim\":\"no_irreversible_effect_outside_allowed_targets\",\"verified_by\":\"sandbox_policy:bubblewrap_unshare_net_fail_closed+policy_kernel_process:file_observation\",\"strength\":\"policy_derived\",\"result\":true,\"evidence_ref\":{\"artifact\":\"runtime_trace\",\"id\":\"trace_events:without_negative_claims\",\"digest\":\"sha256:0fab6f0196134cf5c2674653f67ac11201638e0ca85cbd570d8966a6393962d8\"}}","devtask_validation:{\"task_id\":\"dt-1790932263861-2eel\",\"attempt_number\":2,\"validation_status\":\"passed\",\"validation_reference\":\"dev_tasks.validation_result\"}","devtask_diff_evidence:{\"task_id\":\"dt-1790932263861-2eel\",\"attempt_number\":2,\"diff_stat_sha256\":\"ecf4d9e793c4c8cbcb9e6b0617282cd4f51c780f5e098f92e3e37007a7321cd3\",\"diff_full_sha256\":\"d9fc5e44024ee88604101c8908b6337fe06ed9adfc97a6fd626a0b4130ca6447\",\"scope\":\"diff_stat_and_full\"}","devtask_ci_result:{\"task_id\":\"dt-1790932263861-2eel\",\"attempt_number\":2,\"result_commit\":\"351cf34c60d1f182491a4760289d78a1125dde52\",\"conclusion\":\"success\",\"checked_at\":\"2026-10-02T11:21:14.432Z\",\"source\":\"github_checks_and_statuses\",\"checks\":[{\"name\":\"test\",\"conclusion\":\"success\"}]}","devtask_human_approval:{\"task_id\":\"dt-1790932263861-2eel\",\"attempt_number\":2,\"actor_id\":\"andre-proof\",\"approved_at\":\"2026-10-02 09:14:56\",\"approval_reference\":\"manual-script-approval-02102026\"}","devtask_outcome:{\"task_id\":\"dt-1790932263861-2eel\",\"attempt_number\":2,\"outcome\":\"COMPLETED\",\"completed_at\":\"2026-10-02 11:08:38\",\"reference\":\"dev_tasks.state\"}"],"trace_hash_chain":["8050a43879c7a50c8fccfa22815f45c3a42ec36543eeb086b7010e2705574e49","2944f83ef5cfed816065b46394eee31be39650afd4ed1eb204da9a095e2704e9","af8ae46877c9e483f1169779e3efcba25eaedb543cff992b3a5549abd09992f9","1ad265838013a6055823197fbec6f0b48e9c539a52203794cbee5ed3070ebfc5","21041e38097c7cdbf1d9dcf8fbf5b21a006bc72483d95c326617a2e2eaeffc91","af77bc5302fdeb32927c72017445876a586e3d95d700fda8d0b94271df2e234a","a6fe69d0ae54ca9aadce0d03d39e6a708cffa5ead0bdc2129766398c743d488e","e83c85ab91d9753cb30a6a2161405d97d80593cfb446fe18d3a77c071c676e14","31533ff8ca41a41f2a6e7d8966e3a87b88c19a0c8e51536e706b4b9ec090f70c","df72495f6ac23f9a6abae4c0a9f0e446bc64a4c1e5efe14212492ad2a47e8a6e","0539962b43621c95bb9f288f3008560e4d8ec83ce03397df2e80e7cf3d365410","477fe22b810ff0cce1b195879b9b4ffe56a586749943449460bb0fbfe6adf3e4","d8c8aab8beb7b6f3a30f6766a331eef7de4b61d006461e338277c162b9ba32b6"],"outcome":"verified","signer_key_id":"ed25519:6920d2455a0674889932c505bed1e95457a0e0def1879996916d26a419ea0a85","controller_evidence":{"contract_snapshot":{"schemaVersion":"work-contract@1.0","title":"Lege eine neue Datei debounce.js an mit einer exportierten Funktion debounce(fn, waitMs), die eine entprellte Version vo","rawText":"Lege eine neue Datei debounce.js an mit einer exportierten Funktion debounce(fn, waitMs), die eine entprellte Version von fn zurueckgibt. Mehrfachaufrufe innerhalb des Wartefensters loesen nur einen einzigen Aufruf von fn aus, mit den Argumenten des letzten Aufrufs, nachdem waitMs seit dem letzten Aufruf ohne weiteren Aufruf vergangen sind. Die zurueckgegebene Funktion hat zusaetzlich eine cancel()-Methode, die einen anstehenden, noch nicht ausgefuehrten Aufruf verwirft. Schreibe dazu deterministische Tests in debounce.test.js, die KEINE echte Wartezeit nutzen (z.B. ueber einen injizierbaren/gefaketen Timer statt setTimeout direkt), analog zu rateLimiter.test.js. Aendere ausschliesslich debounce.js und debounce.test.js, keine weiteren Dateien.","acceptanceCriteria":["debounce(fn, waitMs) gibt eine Funktion zurueck, kein Promise.","Mehrere Aufrufe der entprellten Funktion innerhalb des Wartefensters fuehren zu genau einem Aufruf von fn.","Der eine Aufruf von fn erhaelt die Argumente des LETZTEN Aufrufs der entprellten Funktion.","Eine cancel()-Methode auf der zurueckgegebenen Funktion verhindert einen noch ausstehenden Aufruf vollstaendig.","Tests verwenden keine echte Wartezeit -- ein injizierbarer oder gefakter Timer-Mechanismus ist Pflicht.","Bestehende Tests (rateLimiter.test.js, stringUtils.test.js, mathUtils.test.js, jobQueue.test.js, jobQueueStats.test.js, discount.test.js, evidenceWorkflow.test.js) bleiben unveraendert und gruen."],"acceptanceAssertions":[{"description":"Check \"test\" ist erfolgreich","assertion":{"type":"check_passes","checkName":"test"}}],"protectExistingTests":true,"testProtectionDisclosure":"disclosed","riskClass":"R2","capabilityIds":[],"impactTargetIds":[],"engineSelection":null},"policy_snapshot_sha256":"245e86d8cab8c1cfd0722811303a9b363f20e5efa10322dfa96b198c55137d04","producer":"privileged_controller","task_id":"dt-1790932263861-2eel","attempt_number":2,"base_commit":"8e6817a89f36508ddbc32741dfc9a2d3b3ddad2d","result_reference":"github_pr:https://github.com/Alex-Proof/alex-controlled-agent-demo/pull/41","diff_stat_sha256":"ecf4d9e793c4c8cbcb9e6b0617282cd4f51c780f5e098f92e3e37007a7321cd3","diff_full":"diff --git a/debounce.js b/debounce.js\nnew file mode 100644\nindex 0000000..1e591c0\n--- /dev/null\n+++ b/debounce.js\n@@ -0,0 +1,139 @@\n+/**\n+ * debounce -- entprellt eine Funktion.\n+ *\n+ * Idee: Die zurueckgegebene Funktion sammelt Aufrufe. Erst wenn seit dem\n+ * LETZTEN Aufruf `waitMs` Millisekunden ohne weiteren Aufruf vergangen sind,\n+ * wird die zugrunde liegende Funktion `fn` genau EINMAL ausgefuehrt -- und\n+ * zwar mit den Argumenten des letzten Aufrufs. Jeder weitere Aufruf innerhalb\n+ * des Wartefensters startet das Fenster neu (klassisches Trailing-Debounce).\n+ *\n+ * Design-Entscheidungen im Detail:\n+ *\n+ * 1) Der Timer ist injizierbar: `debounce(fn, waitMs)` nutzt standardmaessig\n+ *    das globale setTimeout/clearTimeout, kann aber ueber den optionalen\n+ *    dritten Parameter `{ setTimeout, clearTimeout }` mit einem anderen\n+ *    Timer-Mechanismus versorgt werden. Dadurch koennen Tests einen manuell\n+ *    steuerbaren Fake-Timer injizieren und deterministisch pruefen, ohne auch\n+ *    nur eine Millisekunde echt zu warten.\n+ *\n+ * 2) Rueckgabe ist eine Funktion, kein Promise: Die entprellte Funktion gibt\n+ *    immer `undefined` zurueck. Es wird bewusst kein Promise zurueckgegeben,\n+ *    weil ein entprellter Aufruf womoeglich nie stattfindet (z.B. nach\n+ *    cancel()) -- ein Promise waere also nie aufloesbar. Wer ein Ergebnis\n+ *    braucht, nutzt den Callback.\n+ *\n+ * 3) Nur der letzte Aufruf zaehlt: Bei jedem Aufruf werden die Argumente\n+ *    gespeichert und der laufende Timer verworfen (clearTimeout) und neu\n+ *    gesetzt. Beim Ausloesen wird `fn` mit genau dem zuletzt gespeicherten\n+ *    Argument-Array aufgerufen (\"last call wins\").\n+ *\n+ * 4) cancel() verwirft einen anstehenden Aufruf vollstaendig: Der Timer wird\n+ *    geloescht und die gespeicherten Argumente werden verworfen. Zusaetzlich\n+ *    fuehrt eine Generationen-/Token-Nummer Buch: Der Timer-Callback prueft,\n+ *    ob sein Token noch aktuell ist, und tut sonst nichts. Dadurch bleibt\n+ *    cancel() auch dann korrekt, wenn ein (Fake-)Timer clearTimeout ignoriert\n+ *    und den Callback trotzdem noch ausfuehrt -- ein verworfener Aufruf kann\n+ *    fn so unter keinen Umstaenden erreichen. cancel() ohne anstehenden\n+ *    Aufruf ist ein No-op, und die entprellte Funktion bleibt danach normal\n+ *    weiterbenutzbar.\n+ *\n+ * 5) Genau ein Aufruf pro Wartefenster: Nach dem Ausloesen wird der Zustand\n+ *    (Timer-ID und Argumente) zurueckgesetzt, sodass der naechste Aufruf\n+ *    wieder ein frisches Fenster startet und nicht versehentlich die alten\n+ *    Argumente erneut verwendet.\n+ *\n+ * 6) Fehlverhalten wird sofort abgelehnt (fail-fast, analog zu rateLimiter.js):\n+ *    - fn ist keine Funktion                            -> TypeError\n+ *    - waitMs ist keine endliche Zahl >= 0 (inkl. NaN)   -> RangeError\n+ *    - injizierter setTimeout/clearTimeout ist keine\n+ *      Funktion                                          -> TypeError\n+ *    `waitMs === 0` ist bewusst gueltig: dann laeuft der Aufruf ueber die\n+ *    normale Timer-Warteschlange (asynchron), aber ohne messbare Wartezeit.\n+ */\n+\n+/**\n+ * Entprellt `fn`.\n+ *\n+ * @param {Function} fn Die Funktion, die entprellt aufgerufen werden soll.\n+ * @param {number} waitMs Wartezeit in Millisekunden (endlich, >= 0).\n+ * @param {{ setTimeout?: Function, clearTimeout?: Function }} [options]\n+ *        Optionaler Timer-Mechanismus. Default: globales setTimeout/clearTimeout.\n+ * @returns {Function} Entprellte Funktion mit zusaetzlicher Methode `cancel()`.\n+ */\n+export function debounce(fn, waitMs, options = {}) {\n+  if (typeof fn !== 'function') {\n+    throw new TypeError(`fn muss eine Funktion sein, erhalten: ${typeof fn}`);\n+  }\n+  if (typeof waitMs !== 'number' || !Number.isFinite(waitMs) || waitMs < 0) {\n+    throw new RangeError(\n+      `waitMs muss eine endliche Zahl >= 0 sein, erhalten: ${String(waitMs)}`\n+    );\n+  }\n+\n+  const { setTimeout: injectedSetTimeout, clearTimeout: injectedClearTimeout } = options;\n+\n+  if (injectedSetTimeout !== undefined && typeof injectedSetTimeout !== 'function') {\n+    throw new TypeError(\n+      `options.setTimeout muss eine Funktion sein, erhalten: ${typeof injectedSetTimeout}`\n+    );\n+  }\n+  if (injectedClearTimeout !== undefined && typeof injectedClearTimeout !== 'function') {\n+    throw new TypeError(\n+      `options.clearTimeout muss eine Funktion sein, erhalten: ${typeof injectedClearTimeout}`\n+    );\n+  }\n+\n+  // Default bewusst ueber Wrapper-Funktionen, damit das globale setTimeout\n+  // nicht \"unbound\" aufgerufen wird.\n+  const schedule = injectedSetTimeout ?? ((callback, ms) => globalThis.setTimeout(callback, ms));\n+  const unschedule = injectedClearTimeout ?? ((id) => globalThis.clearTimeout(id));\n+\n+  let timerId = null;\n+  let pendingArgs = null;\n+  let generation = 0;\n+\n+  function debounced(...args) {\n+    // Nur der letzte Aufruf zaehlt: Argumente uebernehmen und das Wartefenster\n+    // neu starten (alten Timer verwerfen).\n+    pendingArgs = args;\n+    generation += 1;\n+    const myGeneration = generation;\n+\n+    if (timerId !== null) {\n+      unschedule(timerId);\n+      timerId = null;\n+    }\n+\n+    timerId = schedule(() => {\n+      // Token-Pruefung: verworfene (oder durch einen neueren Aufruf\n+      // ersetzte) Timer duerfen fn nicht mehr erreichen.\n+      if (myGeneration !== generation) {\n+        return;\n+      }\n+\n+      // Zustand VOR dem Aufruf zuruecksetzen, damit ein erneuter Aufruf aus fn\n+      // heraus nicht mit den alten Argumenten kollidiert.\n+      const callArgs = pendingArgs;\n+      timerId = null;\n+      pendingArgs = null;\n+\n+      fn(...callArgs);\n+    }, waitMs);\n+  }\n+\n+  /**\n+   * Verwirft einen anstehenden, noch nicht ausgefuehrten Aufruf. Ohne\n+   * anstehenden Aufruf passiert nichts. Die entprellte Funktion bleibt danach\n+   * normal weiterbenutzbar.\n+   */\n+  debounced.cancel = function cancel() {\n+    if (timerId !== null) {\n+      unschedule(timerId);\n+      timerId = null;\n+    }\n+    generation += 1;\n+    pendingArgs = null;\n+  };\n+\n+  return debounced;\n+}\ndiff --git a/debounce.test.js b/debounce.test.js\nnew file mode 100644\nindex 0000000..40e4bc7\n--- /dev/null\n+++ b/debounce.test.js\n@@ -0,0 +1,393 @@\n+import { test } from 'node:test';\n+import assert from 'node:assert/strict';\n+import { debounce } from './debounce.js';\n+\n+// --- Fake-Timer: deterministisch, ohne echte Wartezeit ---------------------\n+//\n+// Statt echt zu warten, laeuft die Zeit nur, wenn der Test sie explizit\n+// vorwaerts bewegt (analog zur injizierten Uhr in rateLimiter.test.js). Der\n+// Timer-Mechanismus selbst ist ebenfalls gefakt: setTimeout legt Callbacks in\n+// eine Map, advance() fuehrt die faelligen Callbacks in Reihenfolge ihrer\n+// Ausfuehrungszeit (bei Gleichstand in Anlage-Reihenfolge) aus. Dadurch ist\n+// jeder Testlauf bit-identisch und es wird nie eine Millisekunde geschlafen.\n+function makeFakeTimers() {\n+  let now = 0;\n+  let nextId = 1;\n+  const pending = new Map(); // id -> { callback, runAt }\n+\n+  return {\n+    setTimeout(callback, ms) {\n+      const id = nextId++;\n+      pending.set(id, { callback, runAt: now + ms });\n+      return id;\n+    },\n+    clearTimeout(id) {\n+      pending.delete(id);\n+    },\n+    now: () => now,\n+    pendingCount: () => pending.size,\n+    advance(ms) {\n+      const target = now + ms;\n+      for (;;) {\n+        let next = null;\n+        for (const [id, entry] of pending) {\n+          if (entry.runAt > target) continue;\n+          if (\n+            next === null ||\n+            entry.runAt < next.entry.runAt ||\n+            (entry.runAt === next.entry.runAt && id < next.id)\n+          ) {\n+            next = { id, entry };\n+          }\n+        }\n+        if (next === null) break;\n+        pending.delete(next.id);\n+        now = next.entry.runAt;\n+        next.entry.callback();\n+      }\n+      now = target;\n+    },\n+  };\n+}\n+\n+// Liefert Fake-Timer plus das passende options-Objekt fuer debounce().\n+function makeTimerHarness() {\n+  const timers = makeFakeTimers();\n+  return {\n+    timers,\n+    options: { setTimeout: timers.setTimeout, clearTimeout: timers.clearTimeout },\n+  };\n+}\n+\n+// Sammelt alle Aufrufe von fn inklusive Argumenten.\n+function makeRecorder() {\n+  const calls = [];\n+  const fn = (...args) => {\n+    calls.push(args);\n+    return args.length;\n+  };\n+  return { fn, calls };\n+}\n+\n+const WAIT = 100;\n+\n+// --- Signatur / Rueckgabe --------------------------------------------------\n+\n+test('gibt eine Funktion zurueck (kein Promise) mit cancel-Methode', () => {\n+  const { timers, options } = makeTimerHarness();\n+  const { fn } = makeRecorder();\n+\n+  const debounced = debounce(fn, WAIT, options);\n+\n+  assert.equal(typeof debounced, 'function');\n+  assert.equal(typeof debounced.cancel, 'function');\n+  // Nichts angesetzt, solange nicht aufgerufen wurde.\n+  assert.equal(timers.pendingCount(), 0);\n+});\n+\n+test('der Aufruf der entprellten Funktion liefert kein Promise', () => {\n+  const { options } = makeTimerHarness();\n+  const { fn } = makeRecorder();\n+  const debounced = debounce(fn, WAIT, options);\n+\n+  const result = debounced('a');\n+\n+  assert.equal(result, undefined, 'Rueckgabe ist undefined, kein Promise');\n+  assert.equal(result instanceof Promise, false);\n+});\n+\n+test('ohne injizierten Timer ist das Ergebnis trotzdem einsatzbereit', () => {\n+  // Bewusst OHNE Aufruf: hier wuerde sonst ein echter Timer angesetzt und der\n+  // Test muesste real warten -- das ist in dieser Suite verboten.\n+  const debounced = debounce(() => {}, WAIT);\n+  assert.equal(typeof debounced, 'function');\n+  assert.equal(typeof debounced.cancel, 'function');\n+  assert.doesNotThrow(() => debounced.cancel());\n+});\n+\n+// --- Kernverhalten: genau ein Aufruf, letzte Argumente ---------------------\n+\n+test('mehrere Aufrufe innerhalb des Wartefensters loesen genau einen Aufruf aus', () => {\n+  const { timers, options } = makeTimerHarness();\n+  const { fn, calls } = makeRecorder();\n+  const debounced = debounce(fn, WAIT, options);\n+\n+  // Fuenf Aufrufe, jeweils innerhalb des Fensters.\n+  debounced(1);\n+  timers.advance(20);\n+  debounced(2);\n+  timers.advance(20);\n+  debounced(3);\n+  timers.advance(20);\n+  debounced(4);\n+  timers.advance(20);\n+  debounced(5);\n+\n+  assert.equal(calls.length, 0, 'noch kein Aufruf: Fenster laeuft weiter');\n+\n+  timers.advance(WAIT);\n+\n+  assert.equal(calls.length, 1, 'genau ein Aufruf von fn');\n+});\n+\n+test('der eine Aufruf erhaelt die Argumente des LETZTEN Aufrufs', () => {\n+  const { timers, options } = makeTimerHarness();\n+  const { fn, calls } = makeRecorder();\n+  const debounced = debounce(fn, WAIT, options);\n+\n+  debounced('erste');\n+  debounced('zweite', 2);\n+  debounced('letzte', 3, true);\n+\n+  timers.advance(WAIT);\n+\n+  assert.equal(calls.length, 1);\n+  assert.deepEqual(calls[0], ['letzte', 3, true]);\n+});\n+\n+test('fn wird erst nach waitMs ohne weiteren Aufruf ausgefuehrt (Grenze exakt geprueft)', () => {\n+  const { timers, options } = makeTimerHarness();\n+  const { fn, calls } = makeRecorder();\n+  const debounced = debounce(fn, WAIT, options);\n+\n+  debounced('x');\n+\n+  timers.advance(WAIT - 1);\n+  assert.equal(calls.length, 0, 'bei waitMs - 1 ms darf noch nichts passieren');\n+\n+  timers.advance(1);\n+  assert.equal(calls.length, 1, 'genau bei waitMs wird ausgeloest');\n+  assert.deepEqual(calls[0], ['x']);\n+});\n+\n+test('jeder neue Aufruf startet das Wartefenster neu', () => {\n+  const { timers, options } = makeTimerHarness();\n+  const { fn, calls } = makeRecorder();\n+  const debounced = debounce(fn, WAIT, options);\n+\n+  debounced('a');\n+  timers.advance(90); // fast abgelaufen\n+  debounced('b'); // setzt das Fenster zurueck\n+\n+  timers.advance(90); // 180 ms seit dem ersten, aber nur 90 seit dem letzten Aufruf\n+  assert.equal(calls.length, 0, 'Fenster wurde durch den zweiten Aufruf neu gestartet');\n+\n+  timers.advance(10); // 100 ms seit dem letzten Aufruf\n+  assert.equal(calls.length, 1);\n+  assert.deepEqual(calls[0], ['b']);\n+});\n+\n+test('auch 100 schnelle Aufrufe ergeben genau einen Aufruf mit den letzten Argumenten', () => {\n+  const { timers, options } = makeTimerHarness();\n+  const { fn, calls } = makeRecorder();\n+  const debounced = debounce(fn, WAIT, options);\n+\n+  for (let i = 0; i < 100; i++) {\n+    debounced(i);\n+  }\n+  assert.equal(timers.pendingCount(), 1, 'immer nur genau ein angesetzter Timer');\n+\n+  timers.advance(WAIT);\n+\n+  assert.equal(calls.length, 1);\n+  assert.deepEqual(calls[0], [99]);\n+});\n+\n+test('nach dem Ausloesen ist der Zustand zurueckgesetzt und das naechste Fenster frisch', () => {\n+  const { timers, options } = makeTimerHarness();\n+  const { fn, calls } = makeRecorder();\n+  const debounced = debounce(fn, WAIT, options);\n+\n+  debounced('erste');\n+  assert.equal(timers.pendingCount(), 1);\n+\n+  timers.advance(WAIT);\n+  assert.equal(calls.length, 1);\n+  assert.equal(timers.pendingCount(), 0, 'kein Timer mehr angesetzt');\n+\n+  // Ohne weiteren Aufruf darf sich nichts wiederholen.\n+  timers.advance(WAIT * 10);\n+  assert.equal(calls.length, 1);\n+\n+  debounced('zweite');\n+  timers.advance(WAIT);\n+  assert.equal(calls.length, 2);\n+  assert.deepEqual(calls[1], ['zweite'], 'alte Argumente werden nicht wiederverwendet');\n+});\n+\n+test('zwei entprellte Funktionen sind unabhaengig voneinander', () => {\n+  const a = makeTimerHarness();\n+  const b = makeTimerHarness();\n+  const recA = makeRecorder();\n+  const recB = makeRecorder();\n+  const debouncedA = debounce(recA.fn, WAIT, a.options);\n+  const debouncedB = debounce(recB.fn, WAIT, b.options);\n+\n+  debouncedA('a1');\n+  debouncedA('a2');\n+  debouncedB('b1');\n+\n+  // Nur der Timer von A wird abgelaufen.\n+  a.timers.advance(WAIT);\n+  assert.deepEqual(recA.calls, [['a2']]);\n+  assert.equal(recB.calls.length, 0);\n+\n+  b.timers.advance(WAIT);\n+  assert.deepEqual(recB.calls, [['b1']]);\n+});\n+\n+test('waitMs = 0 ist gueltig und loest genau einen Aufruf aus', () => {\n+  const { timers, options } = makeTimerHarness();\n+  const { fn, calls } = makeRecorder();\n+  const debounced = debounce(fn, 0, options);\n+\n+  debounced('a');\n+  debounced('b');\n+  assert.equal(calls.length, 0);\n+\n+  timers.advance(0);\n+  assert.equal(calls.length, 1);\n+  assert.deepEqual(calls[0], ['b']);\n+});\n+\n+// --- cancel() --------------------------------------------------------------\n+\n+test('cancel() verwirft einen anstehenden Aufruf vollstaendig', () => {\n+  const { timers, options } = makeTimerHarness();\n+  const { fn, calls } = makeRecorder();\n+  const debounced = debounce(fn, WAIT, options);\n+\n+  debounced('a');\n+  assert.equal(timers.pendingCount(), 1);\n+\n+  debounced.cancel();\n+  assert.equal(timers.pendingCount(), 0, 'Timer wurde verworfen');\n+\n+  timers.advance(WAIT * 100);\n+  assert.equal(calls.length, 0, 'fn wurde nie aufgerufen');\n+});\n+\n+test('cancel() ist ohne anstehenden Aufruf ein No-op', () => {\n+  const { timers, options } = makeTimerHarness();\n+  const { fn, calls } = makeRecorder();\n+  const debounced = debounce(fn, WAIT, options);\n+\n+  assert.doesNotThrow(() => debounced.cancel());\n+  assert.doesNotThrow(() => debounced.cancel());\n+  assert.equal(timers.pendingCount(), 0);\n+\n+  timers.advance(WAIT);\n+  assert.equal(calls.length, 0);\n+});\n+\n+test('nach cancel() ist die entprellte Funktion weiterbenutzbar (frisches Fenster)', () => {\n+  const { timers, options } = makeTimerHarness();\n+  const { fn, calls } = makeRecorder();\n+  const debounced = debounce(fn, WAIT, options);\n+\n+  debounced('verworfen');\n+  debounced.cancel();\n+\n+  debounced('gueltig');\n+  timers.advance(WAIT);\n+\n+  assert.equal(calls.length, 1, 'nur der Aufruf nach cancel() zaehlt');\n+  assert.deepEqual(calls[0], ['gueltig']);\n+});\n+\n+test('cancel() verwirft auch dann, wenn der Timer clearTimeout ignoriert (Token-Schutz)', () => {\n+  // Absichtlich naiver Timer: clearTimeout ist ein No-op, die Callbacks\n+  // bleiben also in der Warteschlange stehen -- genau der Fall, in dem ein\n+  // reines clearTimeout den verworfenen Aufruf nicht verhindern koennte.\n+  const queue = [];\n+  const naiveOptions = {\n+    setTimeout(callback) {\n+      queue.push(callback);\n+      return queue.length;\n+    },\n+    clearTimeout() {\n+      // absichtlich leer\n+    },\n+  };\n+  const { fn, calls } = makeRecorder();\n+  const debounced = debounce(fn, WAIT, naiveOptions);\n+\n+  debounced('verworfen');\n+  assert.equal(queue.length, 1);\n+\n+  debounced.cancel();\n+  assert.equal(queue.length, 1, 'naiver Timer kennt kein Abbrechen');\n+\n+  // Alle stehengebliebenen Callbacks ausfuehren: es darf nichts passieren.\n+  for (const callback of queue.splice(0, queue.length)) {\n+    callback();\n+  }\n+  assert.equal(calls.length, 0, 'verworfener Aufruf erreicht fn nicht');\n+});\n+\n+test('ersetzte Timer fuehren auch bei naivem Timer nicht zu Doppelaufrufen', () => {\n+  const queue = [];\n+  const naiveOptions = {\n+    setTimeout(callback) {\n+      queue.push(callback);\n+      return queue.length;\n+    },\n+    clearTimeout() {\n+      // absichtlich leer\n+    },\n+  };\n+  const { fn, calls } = makeRecorder();\n+  const debounced = debounce(fn, WAIT, naiveOptions);\n+\n+  debounced('alte');\n+  debounced('neue');\n+\n+  for (const callback of queue.splice(0, queue.length)) {\n+    callback();\n+  }\n+\n+  assert.equal(calls.length, 1, 'nur der letzte Aufruf loest aus');\n+  assert.deepEqual(calls[0], ['neue']);\n+});\n+\n+// --- Validierung (fail-fast, analog zu rateLimiter.js) ---------------------\n+\n+test('wirft TypeError, wenn fn keine Funktion ist', () => {\n+  assert.throws(() => debounce(undefined, WAIT), TypeError);\n+  assert.throws(() => debounce(null, WAIT), TypeError);\n+  assert.throws(() => debounce('keine Funktion', WAIT), TypeError);\n+  assert.throws(() => debounce({}, WAIT), TypeError);\n+});\n+\n+test('wirft RangeError bei ungueltigem waitMs', () => {\n+  const { fn } = makeRecorder();\n+  assert.throws(() => debounce(fn, -1), RangeError);\n+  assert.throws(() => debounce(fn, NaN), RangeError);\n+  assert.throws(() => debounce(fn, Infinity), RangeError);\n+  assert.throws(() => debounce(fn, '100'), RangeError);\n+  assert.throws(() => debounce(fn), RangeError);\n+});\n+\n+test('wirft TypeError bei ungueltigem injiziertem Timer', () => {\n+  const { fn } = makeRecorder();\n+  assert.throws(() => debounce(fn, WAIT, { setTimeout: 123 }), TypeError);\n+  assert.throws(() => debounce(fn, WAIT, { clearTimeout: 'nein' }), TypeError);\n+});\n+\n+// --- Guard: keine echte Wartezeit in dieser Suite --------------------------\n+\n+test('diese Testsuite nutzt ausschliesslich injizierte Fake-Timer', () => {\n+  // Dokumentiert die Anforderung als Test: debounce() akzeptiert den\n+  // Timer-Mechanismus als Parameter, und die Tests oben rufen ihn immer mit\n+  // options auf -- es gibt in dieser Datei genau einen Aufruf ohne options,\n+  // und der loest bewusst keinen Timer aus.\n+  const { timers, options } = makeTimerHarness();\n+  const { fn, calls } = makeRecorder();\n+  const debounced = debounce(fn, WAIT, options);\n+\n+  debounced('deterministisch');\n+  timers.advance(WAIT);\n+\n+  assert.deepEqual(calls, [['deterministisch']]);\n+  assert.equal(timers.now(), WAIT);\n+});","diff_full_sha256":"d9fc5e44024ee88604101c8908b6337fe06ed9adfc97a6fd626a0b4130ca6447","execution_reference":"dev_task_attempts:19","validation_reference":"dev_tasks.validation_result","approval_reference":"manual-script-approval-02102026","sandbox_reference":"sandbox-attestation:sha256:39bd0b9a4d2ef1f51fa541d0d2b5d5c796b22886aa238976eab0aa6361fd5b01","sandbox_attestation":{"schema_version":"sandbox-attestation@2.0","sandbox_id":"sandbox:devtask-dt-1790932263861-2eel-1790932387496:43d7743796a68ab0","agent_id":"devtask-dt-1790932263861-2eel-1790932387496","started_at":"2026-10-02T09:13:08.855Z","completed_at":"2026-10-02T09:14:14.757Z","snapshot_sha256":"43d7743796a68ab03ea631bfa29a684bc3b56fa1be69b7fb8604d4d67da5ef2d","policy_sha256":"dc0a29632e53f37f3d02426ae9c3da65b557a53185265da9cd5d0f7c8cb26189","os_isolation_available":true,"git_metadata_absent":true,"controller_checkout_separated":true,"network_policy":"bubblewrap_unshare_net_fail_closed","environment_policy":"bubblewrap_clearenv","process_policy":"systemd_scope_limits_and_kill","handoff_manifest_sha256":"f7150313e80e90f6f2d46121d6535b534e51a0fc643aedf5166c266f02c7f07d","rejected_manifest_sha256":"4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945","network_capture":null,"file_observation":{"schema_version":"policy-kernel-file-observation@1.0","observer":"policy_kernel_process","kernel_pid":570732,"allowed_write_paths":["."],"inspected_entry_count":22,"blocked_attempt_count":0,"completed_at":"2026-10-02T09:14:14.500Z","os_write_deny_observer":{"attempted":false,"active":false,"incomplete":false}},"policy_kernel_observer_receipt":{"schema_version":"policy-kernel-observer-receipt@1.0","observer_url":"http://10.84.20.3:8420","leaf_index":1827,"received_at":"2026-10-02T09:14:14.528Z","bundle_id":"devtask-dt-1790932263861-2eel-1790932387496","run_id":"dt-1790932263861-2eel:2","observation_digest_sha256":"806b98215dc3f3241195880801d0c10c991e58daf1e57133bd5292ffa49f64c3"}},"agent_run_id":"devtask-dt-1790932263861-2eel-1790932387496","repository_state":{"schema_version":"repository-state@1.0","base_commit":"8e6817a89f36508ddbc32741dfc9a2d3b3ddad2d","result_commit":"351cf34c60d1f182491a4760289d78a1125dde52","tree_before_sha256":"b54a50db941d05bbf23dad7c3bd5b0e1e400693ceaebd8f5306eab482d0868aa","tree_after_sha256":"b9ebc946ecbf35901d8625145bcbf1eae8bebead4df433c0ac865d780beb7b5c","changed_files":[{"path":"debounce.js","status":"A","before_sha256":null,"after_sha256":"e8d1a9e005d94b9bf7d6af1e6c073d8b519aa9d94057325a40f0fa5ab37c28c2"},{"path":"debounce.test.js","status":"A","before_sha256":null,"after_sha256":"37fa44094e7d47966d9a93b0d191b845e5095316a48cdd68aebada33a15f836a"}]},"test_integrity_policy":{"schema_version":"test-integrity-policy@1.0","policy":"protect_existing_tests","protected":true,"status":"passed","violating_files":[]},"context_sources":{"schema_version":"context-sources@1.0","title":"Context Sources / Injection Surface","sources":[{"id":"268d788e-b4e5-418b-b46f-83c4493746f2","kind":"web_content","origin_ref":"https://github.com/Alex-Proof/alex-controlled-agent-demo/readme","trust_level":"unverified","ingested_at":"2026-10-02T09:13:07.415Z","triggered_tool_execution":false,"triggered_tools":[]},{"id":"3069598d-a772-43f6-af63-4f4bfd3d969a","kind":"user_input","origin_ref":"manual:manual-debounce-1790932263858","trust_level":"trusted","ingested_at":"2026-10-02T09:13:07.414Z","triggered_tool_execution":false,"triggered_tools":[]},{"id":"466104a6-b14d-4e1a-8dc4-38976851f4ec","kind":"user_input","origin_ref":"manual:manual-debounce-1790932263858","trust_level":"trusted","ingested_at":"2026-10-02T09:11:19.951Z","triggered_tool_execution":false,"triggered_tools":[]},{"id":"85dbc278-db5b-4dba-9bf8-ac826d0f3d99","kind":"web_content","origin_ref":"https://github.com/Alex-Proof/alex-controlled-agent-demo/readme","trust_level":"unverified","ingested_at":"2026-10-02T09:11:19.952Z","triggered_tool_execution":false,"triggered_tools":[]},{"id":"a6aa9953-2cf0-4f94-a1d2-8284b4124608","kind":"web_content","origin_ref":"https://api.github.com/repos/Alex-Proof/alex-controlled-agent-demo/commits","trust_level":"unverified","ingested_at":"2026-10-02T09:11:19.953Z","triggered_tool_execution":false,"triggered_tools":[]},{"id":"f8e51b12-b4fc-4e15-a207-a0f597003f06","kind":"web_content","origin_ref":"https://api.github.com/repos/Alex-Proof/alex-controlled-agent-demo/commits","trust_level":"unverified","ingested_at":"2026-10-02T09:13:07.416Z","triggered_tool_execution":false,"triggered_tools":[]}]}},"required_evidence":["contract","attempt","execution_context","validation","outcome","base_commit","diff_full","human_approval","result_reference","ci_result","repository_state","test_integrity_policy"],"approval_attestation_required":true,"customer_evidence":{"schema_version":"customer-evidence@1.0","brief":{"title":"Lege eine neue Datei debounce.js an mit einer exportierten Funktion debounce(fn, waitMs), die eine entprellte Version vo","raw_text":"Lege eine neue Datei debounce.js an mit einer exportierten Funktion debounce(fn, waitMs), die eine entprellte Version von fn zurueckgibt. Mehrfachaufrufe innerhalb des Wartefensters loesen nur einen einzigen Aufruf von fn aus, mit den Argumenten des letzten Aufrufs, nachdem waitMs seit dem letzten Aufruf ohne weiteren Aufruf vergangen sind. Die zurueckgegebene Funktion hat zusaetzlich eine cancel()-Methode, die einen anstehenden, noch nicht ausgefuehrten Aufruf verwirft. Schreibe dazu deterministische Tests in debounce.test.js, die KEINE echte Wartezeit nutzen (z.B. ueber einen injizierbaren/gefaketen Timer statt setTimeout direkt), analog zu rateLimiter.test.js. Aendere ausschliesslich debounce.js und debounce.test.js, keine weiteren Dateien."},"cost_partial":{"cost_usd":0.02152,"cost_usd_tracked":true,"turns_used":10,"duration_seconds":67,"assigned_model":"deepseek-v4-flash","assigned_engine":"deepseek","token_tracking":"not_available"},"denied":[],"approval":{"proven":true,"actor_id":"andre-proof","channel":"manual","confirmed_by":"andre-proof","confirmed_at":"2026-10-02 09:11:03","approved_at":"2026-10-02 09:14:56","approval_reference":"manual-script-approval-02102026"},"open_risks":[{"code":"coverage_not_measured","description":"Ob der geaenderte Code durch Tests abgedeckt ist, wird nicht automatisch gemessen -- strukturelle Grenze, keine Aussage ueber diesen einzelnen Lauf.","recommended_action":"Optional: Testabdeckung des geaenderten Codes separat pruefen (z.B. Coverage-Tool), falls fuer diesen Auftrag wichtig."},{"code":"domain_correctness_not_verified","description":"Ob die Aenderung fachlich das Richtige tut, wurde nicht automatisiert geprueft -- das bleibt Teil der menschlichen Freigabe.","recommended_action":"Fachliche Korrektheit durch menschliches Review oder eine Domaenen-Pruefung bestaetigen lassen."},{"code":"dependency_tree_not_fully_audited","description":"Nur neu eingefuehrte Abhaengigkeiten wurden gegen bekannte Schwachstellen (OSV.dev) geprueft, nicht der gesamte bestehende Abhaengigkeitsbaum des Ziel-Repos.","recommended_action":"Fuer eine vollstaendige Pruefung ein dediziertes SBOM-/Dependency-Audit-Tool auf dem gesamten Repo einsetzen."}],"acceptance_assertions":[{"description":"Check \"test\" ist erfolgreich","satisfied":true}],"customer_summary":"Lege eine neue Datei debounce.js an mit einer exportierten Funktion debounce(fn, waitMs), die eine entprellte Version vo\n\nAuftrag:\nLege eine neue Datei debounce.js an mit einer exportierten Funktion debounce(fn, waitMs), die eine entprellte Version von fn zurueckgibt. Mehrfachaufrufe innerhalb des Wartefensters loesen nur einen einzigen Aufruf von fn aus, mit den Argumenten des letzten Aufrufs, nachdem waitMs seit dem letzten Aufruf ohne weiteren Aufruf vergangen sind. Die zurueckgegebene Funktion hat zusaetzlich eine cancel()-Methode, die einen anstehenden, noch nicht ausgefuehrten Aufruf verwirft. Schreibe dazu deterministische Tests in debounce.test.js, die KEINE echte Wartezeit nutzen (z.B. ueber einen injizierbaren/gefaketen Timer statt setTimeout direkt), analog zu rateLimiter.test.js. Aendere ausschliesslich debounce.js und debounce.test.js, keine weiteren Dateien.\n\nKosten:\n$0.02, 10 Turns, 1m 7s\nEs werden ausschließlich Aggregatkosten erfasst, keine Tokenzahlen.\n\nFreigabe:\nBewiesen freigegeben über Kanal `manual`.\nBestätigt von: andre-proof\nBestätigt am: 2026-10-02 09:11:03\n\nAbgelehnte Aktionen:\nKeine abgelehnten Aktionen in diesem Lauf.\n\nOffene Risiken:\n- Ob der geaenderte Code durch Tests abgedeckt ist, wird nicht automatisch gemessen -- strukturelle Grenze, keine Aussage ueber diesen einzelnen Lauf.\n- Nur neu eingefuehrte Abhaengigkeiten wurden gegen bekannte Schwachstellen (OSV.dev) geprueft, nicht der gesamte bestehende Abhaengigkeitsbaum des Ziel-Repos.\n- Ob die Aenderung fachlich das Richtige tut, wurde nicht automatisiert geprueft -- das bleibt Teil der menschlichen Freigabe.\n\nErfolgskriterien:\n1/1 maschinell pruefbare Kriterien erfuellt."},"signature":"MDEG9QozyWRXp6kqoxeR5p/tRYIY5mnDfHOhNel0stV7Tl2C303fkDY+vjZ74Y+1soshbZcJU4g4i12MeWRZDA==","public_key":"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA7ne5p+CojXWK66SIZl3MuGoyZ/X6WkWNG+ldf8TASWM=\n-----END PUBLIC KEY-----\n","rfc3161_timestamp":{"schema_version":"rfc3161-timestamp@1.0","tsa_url":"https://freetsa.org/tsr","timestamped_sha256":"14d1a98c5468b43555d46c1630ff1cc0b7644f0158ee7210fd365464355b0b95","gen_time":"2026-10-02T11:21:15.000Z","token_der_base64":"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"},"approval_attestation":{"schema_version":"approval-attestation@1.0","actor_id":"andre-proof","approved_at":"2026-10-02 09:14:56","bundle_sha256":"a52d159a8966f1512580003ca910d537b4c1bcc6c95172c9b49e8cebe15e4ed0","signer_key_id":"ed25519:267a6a2a921b986b0a1db88022f44da947be353b4f93e3242c5b39c3f5708632","public_key":"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEABvVvU5ZqOk3yjII9iF6QpD0T+AWDWWTsr34P7R9eXvk=\n-----END PUBLIC KEY-----\n","signature":"nc/yAKX1MSlEbYdfnDXstweWRgkCnRHqY94uL/oTiHO4n8WBSFbzIiya9+IrDZqARQ9EBeyz9NJfrOR0P69zDQ=="},"observer_receipt":{"schema_version":"observer-receipt@1.0","observer_url":"http://10.84.20.3:8420","leaf_index":1838,"received_at":"2026-10-02T11:21:15.419Z"},"epistemic_gate":{"schema_version":"epistemic-gate-attestation@1.0","gate":{"schema_version":"epistemic-gate@1.0","score":0,"threshold":0.34,"blind_modules":[],"decision":"acted","evaluated_at":"2026-10-02T11:21:15.438Z"},"bundle_sha256":"cbd4ba1a08380ba2dd3a7d681072ba11b94be8b16aa0f32d590e18e139bfad72","signer_key_id":"ed25519:6920d2455a0674889932c505bed1e95457a0e0def1879996916d26a419ea0a85","public_key":"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA7ne5p+CojXWK66SIZl3MuGoyZ/X6WkWNG+ldf8TASWM=\n-----END PUBLIC KEY-----\n","signature":"v1mM3SZV4ltxHm5Y0zAxYiq/u6glHcmmpk/Lr7bW55J/cLZpe7b8deBj2Mlipwc2PN4mu/pHyUZnEvhQ4ALlBw=="}}