live · updated 2026-07-19 17:39:22 MDT · auto-refreshes every 20s
base d83595e3 · 3 agents on disjoint surfaces, own branch + rolling PR each · 23 fixes shipped to main
each agent owns a disjoint surface cluster and works highest-value-first; refill via a scoped bug-hunt when a lane empties
harden/agent-1 · no open PR yet
surfaces: docktor/Services/SpotifyAPI*.swift, docktor/Services/NowPlayingManager.swift, docktor/Services/LyricsService.swift, docktor/Services/WeatherManager.swift, docktor/Widgets/Music/**, docktor/Widgets/Weather/**
harden/agent-2 · PR #150 ↗ Harden marathon — Agent 2 (Feedback/Telemetry/Worker)
surfaces: docktor/Services/Feedback/**, docktor/Core/Telemetry/**, feedback-worker/**
harden/agent-3 · no open PR yet
surfaces: docktor/DockPreviews/**, docktor/Panel/**, docktor/Services/AppleCalendarAPI.swift, docktor/Services/UnifiedCalendarService.swift, docktor/Services/CalendarEventParsing.swift, docktor/Widgets/Calendar/**
Visible fixes a beta user experiences directly
Invisible robustness / privacy / security wins
| # | Fix | Severity | User value | Dev value | What it means for you | What it means for the codebase | Tests | Commit |
|---|---|---|---|---|---|---|---|---|
| 1 | Weather day/night stays correct near the poles Weather | medium | Medium | Medium | Hourly sun/moon icons stay correct in far-north/south locations. Before, a polar day where Open-Meteo omits only sunrise (or only sunset) shifted every following day’s sun times, mislabeling day as night for the rest of the forecast. | parseWeatherResponse builds sunrise/sunset from the SAME kept indices so isDaylight’s by-index pairing can’t misalign after an asymmetric null. +2 tests (asymmetric-null alignment). | +2 | 500b96afCI (macOS) |
| 1 | Weather widget survives null hourly stats too Weather | high | High | Medium | Extends the earlier polar-null fix: the Weather widget no longer blanks when Open-Meteo omits an hourly humidity, wind, or UV value (which it can past the forecast horizon). | Made the hourly relativeHumidity2m/windSpeed10m/uvIndex arrays optional-ELEMENT ([Int?]?/[Double?]?) and nil-safe at the access sites; a single null used to throw the whole decode. +1 decode test. | +1 | 151b9654CI (macOS) |
| 1 | Widgets no longer snap mid-swipe when the Dock blinks out Dock widgets · space swipe | medium | Medium | Medium | When you swipe between Spaces, the widget bar that follows the Dock no longer jerks to its end position if the Dock's accessibility frame momentarily disappears mid-swipe (a Dock relaunch or permission blip). The slide stays smooth and only settles if the Dock stays unreadable. | WidgetManager.followDockTick aborted the whole gesture on the FIRST nil AX frame, snapping the still-mid-slide panels. Extracted a pure DockFollowerNilFrameGate that tolerates up to 4 consecutive nil frames (~100 ms at 30 Hz) and resets on any readable frame, so only a sustained blackout aborts. Abort behavior unchanged; 3 s safety deadline still bounds the gesture. | docktorTests/DockFollowerNilFrameGateTests.swift: +6 tests (single-nil tolerate, tolerance boundary abort, readable-frame reset, fresh-start reset, degenerate tolerance, default-tolerance blackout window). | fa01de9escripts/build.sh debug + build-for-testing green in isolated worktree; DockFollowerNilFrameGateTests 6/6 pass locally; CI xctest authoritative. |
| 1 | Weather widget survives partial Open-Meteo data Weather | high | High | High | The Weather widget no longer goes blank where Open-Meteo omits a value — e.g. polar sunrise/sunset, or precipitation past the forecast horizon. Before, one missing value wiped current + hourly + daily. | The Open-Meteo Codable arrays (precip / sunrise / sunset / UV) now accept null elements; a null sun event is skipped and a null precip/UV reads as 0. +2 tests including a null-laden decode that threw before the fix. | +2 | 68a90028CI (macOS) |
| 2 | Music scrubber can’t show a time past the end Music · player scrubber | low | Low-Med | Low | The elapsed-time label on the music scrubber no longer shows an impossible time like “4:00 / 3:30”. The position ticks locally between player updates; if one was missed it kept counting past the end until the next poll (~30s). | Added FormatHelpers.clampedPosition(_:duration:) (floors at 0, caps at duration, passes through when duration unknown) and used it for the displayed position; the progress fill was already clamped. +3 unit tests. | +3 | 8d1d1a1aCI (macOS) |
| 2 | Reduce Motion now also stills the widget swipe-follow slide Dock widgets · space swipe / auto-hide | low | Medium | Medium | With General > Animations turned off, the widget bar that follows the Dock during a Space swipe or auto-hide no longer animates its slide and then jumps at the end — it now snaps in step with the Dock, matching the rest of the reduced-motion experience. | setPanelSlideOffsetSmoothed animated the content-layer transform unconditionally, while the settle leg (finishSwipeSlide) already zeroed its animation duration when animations were disabled — so follow and settle disagreed. Extracted a pure SmoothedSlideMotion policy (animate vs snap) gated on AnimationPreset.animationsEnabled and wired it into both continuous-follow call sites. | docktorTests/SmoothedSlideMotionTests.swift: +4 tests (animate when enabled, snap when disabled, snap on non-positive duration, real follower-smoothing constant animates). | 4bd8d933scripts/build.sh debug + build-for-testing green in isolated worktree; SmoothedSlideMotionTests 4/4 pass locally; CI xctest authoritative. |
| 2 | Recently Played shows “X minutes ago” again Music · Recently Played | high | High | Medium | The “X minutes ago” line is back on every Recently Played row. Real Spotify timestamps include milliseconds the app couldn’t parse, so the relative time silently disappeared for everyone. | played_at now parses both fractional- and whole-second ISO8601 (with fallback); the new regression test uses a real millisecond payload that would have failed before. Also capped a previously-unbounded 401 retry loop in search(). | +3 (incl. fractional fixture) | 0315f65eCI (macOS) |
| 3 | Synced lyrics honor the LRC offset tag Music · lyrics | low | Low-Med | Low | Synced lyrics that ship with an [offset] correction now line up with the music instead of running consistently early or late for the whole song. | parseLRC now parses the LRC [offset:±ms] metadata tag and shifts every timestamp (positive = earlier), clamped at 0; previously the tag was silently dropped. +2 pure parser tests. | +2 | f120765dCI (macOS) |
| 3 | Gray/white custom calendar colors save correctly again Calendar · custom colors | low | Low | Medium | Choosing a gray, white, or black custom calendar color no longer stores a wrong bright hue. Color.toHex() previously turned white into #FFFF00 (yellow) and 50% gray into #80FF00 (green) for any grayscale-backed color. | Color.toHex() read NSColor(self).cgColor.components positionally as [r,g,b], but a grayscale CGColor has only [white, alpha] — so alpha was read as green and blue defaulted to 0 (confirmed with a live NSColor probe). Now converts via usingColorSpace(.sRGB) before reading channels, matching the already-correct hexString(from:) path. RGB inputs unchanged. | docktorTests/CalendarColorTests.swift: +3 regression tests (grayscale-backed white/black/mid-gray). Existing toHex tests only used sRGB Color(red:green:blue:), so never hit the buggy branch. | adea931dscripts/build.sh debug + build-for-testing green; CalendarColorTests 19/19 + CalendarColorManagerTests 5/5 pass locally; live NSColor probe confirmed repro + fix; CI xctest authoritative. |
| 3 | Crash reports stop leaking your username Feedback · crash reports | high | High (privacy) | High | Crash reports sent from the beta no longer expose your Mac username. The old redaction missed bare home-folder paths and could even leak a second username by “bridging” across text. | redact() rewritten to match a single username segment (no whitespace/newline bridging), handle no-trailing-slash paths, and preserve the real /Users/Shared folder. | +3 | abd2a5e7CI (macOS) |
| 4 | Now-playing position stops at the end of the track Music · playback position | low | Low-Med | Medium | Fixes the root cause behind the scrubber overrun: the playback clock that ticks between player updates now stops at the track end instead of counting past it, so anything reading the position (scrubber, lyrics sync) stays truthful even if an update is missed. | Extracted a pure NowPlayingArbitration.tickedPosition(referenceValue:elapsed:duration:) that clamps to [0, duration] (floors at 0 when duration unknown) and routed tickPosition through it. +3 pure unit tests. | +3 | 6e7ac1b2CI (macOS) |
| 4 | Calendar countdown logic is now single-sourced and tested Calendar · countdown | low | Low | High | No behavior change for users; the calendar's "in N min" label and the pulsing final-countdown share one implementation now, so a future tweak to those thresholds stays consistent between the widget and the rest of the app. | UnifiedCalendarEvent duplicated CalendarEventParsing.timeUntil inline and kept isUrgent/isFinalCountdown/finalCountdownSeconds as untestable Date()-reading properties. Moved the countdown classification into CalendarEventParsing as pure (start:now:) functions and delegated the properties to them. finalCountdownSeconds now reads `now` once (was two Date() reads that could disagree across a tick). Verbatim move; existing property-level suites stay green. | docktorTests/CalendarEventParsingTests.swift: +3 deterministic kernel tests (urgent/final-countdown windows, ceil rounding + nil-outside-window). CalendarEventParsing/UnifiedCalendarEvent/CalendarCountdownState suites 64/64 pass locally. | 181202dascripts/build.sh debug + build-for-testing green in isolated worktree; 64/64 across the 3 affected suites pass locally; CI xctest authoritative. |
| 4 | Nothing is sent before you’ve seen the disclosure Feedback · telemetry consent | high | High (consent) | High | A crash that happened before you finished onboarding can no longer be transmitted early. Nothing about a crash leaves your Mac until you’ve actually seen the telemetry disclosure and had the chance to opt out. | CrashReportHarvester now requires the TelemetryConsent watermark AND the opt-out toggle, matching the heartbeat gate. The old code checked only the toggle, so a pre-onboarding report could flush on the next launch. | +1 new, 8 adapted | d77e275dCI (macOS) |
| 5 | Calendar countdown thresholds now single-sourced everywhere Calendar · countdown | low | Low | Medium | No behavior change; internal cleanup so the calendar's urgent/final-countdown thresholds live in exactly one place. | AppleCalendarEvent held a third inline copy of the 300 s urgent / 5 s final-countdown thresholds + ceil rounding (its timeUntil already delegated to CalendarEventParsing; the classifiers did not). Delegated isUrgent/isFinalCountdown/finalCountdownSeconds to the kernel so CalendarEventParsing is the single source of truth across AppleCalendarEvent and UnifiedCalendarEvent. Behavior identical. | Covered by the CalendarEventParsing kernel tests added in the previous commit; calendar suites 64/64 pass locally. | 210f6e1cscripts/build.sh debug + build-for-testing green in isolated worktree; calendar suites pass locally; CI xctest authoritative. |
| 5 | Feedback dashboard auth bypass closed Feedback backend (Cloudflare Worker) | critical | High (PII) | Critical | Your submitted feedback, screenshots, and install ID can no longer be read by a stranger. The ops dashboard trusted a header that anyone could fake on the default deployment. | isDashboardAuthorized no longer trusts the spoofable Cf-Access-Authenticated-User-Email header unless CF_ACCESS_ENABLED is explicitly set; otherwise it requires the signed session cookie. Fully verified locally. | +2 (npm 74/74 green) | 3cb2c61enpm 74/74 (local, full) |
| 6 | Queued feedback can no longer double-send or vanish Feedback · retry queue | medium | Med-High | Medium | A queued feedback report (or harvested crash summary) is now sent exactly once. Before, the launch drain racing the crash-harvester drain could send the same report twice — or silently drop a report that arrived while another was sending. | flushPending collapses overlapping drains behind an in-flight flag, re-reads the live queue every iteration, and removes sent items by value instead of position. New FeedbackSending protocol seam + a gated test sender make the interleavings deterministically testable. | +2 (deterministic interleaving) | 803a9da6CI (macOS) |
| 7 | Up Next survives a podcast in the queue Music · Up Next | medium | Medium | Medium | Queueing a podcast episode (or listening to one) no longer wipes the whole Up Next list. Episodes now show up in the queue with the show's name where the artist goes. | SpotifyQueueItem discriminates on the payload's `type`: episodes decode into a minimal SpotifyEpisode (show + images) instead of failing SpotifyTrack's artists/album requirements; items without a discriminator keep the strict track shape so malformed tracks still fail loudly. | +2 (mixed track/episode fixture) | 11a74967CI (macOS) |
| 8 | Only real meeting links auto-open at meeting start Calendar · meeting join | medium | Medium (safety) | Medium | An event whose URL merely mentions a meeting service (a tracking redirect, a lookalike domain like zoom.us.evil.example) is no longer treated as a video call — which matters because docktor auto-opens that link when the meeting starts. | isVideoConferenceURL now parses the URL and matches the real host — exact for meet.google.com / teams.microsoft.com / facetime.apple.com, domain-suffix for zoom.us / webex.com — http(s) only, case-insensitive. Substring tricks via path, query, userinfo, or host suffix all fail closed. | +2 | 0c774471CI (macOS) |
| 9 | Lyrics stay in sync through repeated choruses Music · Lyrics | low-medium | Medium | Medium | Songs whose lyrics compress repeated choruses no longer show raw [01:45.00] tags in the middle of a line, the chorus highlights every time it comes around, and one odd timestamp can no longer freeze the karaoke highlight for the rest of the song. | parseLRC consumes every stacked [mm:ss.xx] tag (one entry per timestamp, time-sorted, unique post-sort ids); the position tracker is a full-scan currentLineID kernel shared with the test seam instead of an early-break walk plus a hand-drifted copy. | +3 | 97c1a16bCI (macOS) |
| 10 | Heartbeat metrics can't be flooded or poisoned Feedback backend (Cloudflare Worker) | medium | Low | Medium-High | No visible change — this hardens the anonymous liveness ping's backend so beta metrics stay trustworthy. | uptimeSeconds clamps to [0, one year] and non-finite input stores 0 (Infinity used to reach the D1 bind); the heartbeat_events append self-guards in SQL (NOT EXISTS a same-install row newer than 60s, same single batch), so a token-holding spammer can't grow the table or the derived metrics without bound. App pings every 120s — honest events are never dropped. | +2 (npm 76/76 green) | eef33c74npm 76/76 (local, full) |
| 11 | Feedback backend shrugs off malformed URLs and cookies Feedback backend (Cloudflare Worker) | medium | Low | Medium | No visible change — the ops dashboard's edges got sturdier. | A stray %zz in a dashboard/blob path or a garbage session cookie threw URIError out of decodeURIComponent into the top-level catch: an unauthenticated 500 plus log noise. safeDecode falls back to the raw string, which then fails closed through each path's own strict lookup (302 to login / 404). | +1 (npm 77/77 green) | 92c10241npm 77/77 (local, full) |
| 12 | Join links found on company Zoom/Webex domains Calendar · meeting join | low | Low-Medium | Low-Medium | Events whose notes carry a company meeting link like my-team.webex.com or eu01-x.zoom.us now get their Join button — hyphenated company subdomains (and uppercase pastes) were silently dropped before. | Free-text extraction patterns allow hyphenated multi-level subdomain segments for Zoom/Webex and match case-insensitively via (?i), while the service domain stays anchored immediately before the first path slash so lookalike hosts still never match. | +2 | f8e0acc6CI (macOS) |
| 13 | One report, one email — even when the network hiccups Feedback backend (Cloudflare Worker) | low-medium | Low-Medium | Medium | If your feedback went through but the reply got lost, the app's automatic retry no longer files the same report twice (and the maintainer no longer gets two alert emails about it). | Every /submit runs a dedupe probe (15-min window on category + message + meta_json) before writing; a verbatim retry returns the original id with deduplicated: true and skips row, blobs, and email. meta.submittedAt is stamped at payload creation, so distinct reports never collide. No schema change. | +2 (npm 79/79 green) | 862c3bbfnpm 79/79 (local, full) |
| # | Target (surface) | User value | Severity | Conf | Cost | Score | Status |
|---|---|---|---|---|---|---|---|
| 1 | Spotify Recently-Played timestamp parse + bounded search retry (Music) — bare ISO8601DateFormatter drops fractional played_at → "Xm ago" vanishes every row | High | High | High | S | 78 | LANDED — 0315f65e |
| 2 | Worker dashboard auth bypass via spoofable Cf-Access-Authenticated-User-Email (feedback-worker src/index.js:909-917) — any request with that header reads all beta-tester PII; default deploy has no Access app in front | High (PII) | Critical (security) | High | S | 96 | LANDED — 3cb2c61e (npm test 74/74, fully verified) |
| 3 | Crash-report redaction leaks usernames (CrashReportParser.swift:155-159) — regex misses /Users/x w/o trailing slash and greedy-bridges across text/newlines, leaking a second username | High (PII) | High (security) | High | S | 90 | LANDED — abd2a5e7 (build + test-target compile green; full run on CI) |
| 4 | Crash harvester ignores consent watermark (CrashReportHarvester.swift:68-71) — can transmit a crash summary before onboarding/opt-out (heartbeat gates on both; harvester doesn't) | High (consent) | High (privacy) | High | S | 88 | LANDED — d77e275d (build + test-target compile green; full run on CI) |
| 5 | Weather: non-optional Open-Meteo arrays (WeatherManager.swift:176,200-203) — one null (polar sunrise/sunset, null precip) fails decode → entire forecast blanks | High (widget dead) | High | High | S | 84 | LANDED — 68a90028 (build + test-target compile green; full run on CI) |
| 6 | FeedbackService flushPending re-entrancy (FeedbackService.swift:191-205) — boot + harvest drains overlap → duplicate sends / lost queue items | Med-High | Med | High | M | 72 | LANDED — 803a9da6 (seamed via FeedbackSending; full run on CI) |
| 7 | Spotify queue decode fails on podcast episode (SpotifyAPI.swift:44-59) — a podcast in the queue throws → whole "Up Next" blanks | Med | Med | Med-High | M | 70 | LANDED — 11a74967 (typed SpotifyQueueItem; full run on CI) |
| 8 | Calendar isVideoConferenceURL substring match (CalendarEventParsing.swift:48-57) — lookalike/redirect URLs classified as meetings and auto-opened at start | Med | Med (auto-open) | High | S | 68 | LANDED — 0c774471 (host-anchored; full run on CI) |
| 9 | Lyrics multi-timestamp LRC lines (LyricsService.swift:392,399-406) — compressed chorus tags render raw [mm:ss] text and only highlight once | Med | Low-Med | Med | S | 60 | LANDED — 97c1a16b (with #10, same-file pair) |
| 10 | Lyrics updateCurrentIndex non-monotonic freeze (LyricsService.swift:435-442) — early break freezes highlight after any out-of-order timestamp | Med | Low-Med | Med | S | 58 | LANDED — 97c1a16b (with #9; kernel-extracted, seam delegates) |
| 11 | Worker /beta/heartbeat unbounded (src/index.js:222-281) — no rate limit, unbounded uptime → D1 growth / metric pollution (install_id cap already landed on main; backlog note part-stale) | Med (dev) | Med | High | S | 56 | LANDED — eef33c74 (npm test 76/76, fully verified locally) |
| 12 | Worker malformed cookie/path → unauth 500 (src/index.js:982-991,66) — decodeURIComponent throws → 500 instead of fail-closed 302; add safeDecode | Low-Med (dev) | Med | High | S | 50 | LANDED — 92c10241 (npm test 77/77, fully verified locally) |
| 13 | Calendar meet regex misses hyphenated vanity subdomains + case (CalendarEventParsing.swift:72-79) | Low-Med | Low | High | S | 48 | LANDED — f8e0acc6 (full run on CI) |
| 14 | Worker /submit no idempotency → duplicate rows/blobs/emails on client retry (src/index.js:151-209) | Low-Med | Low-Med | High | M | 44 | LANDED — 862c3bbf (npm test 79/79, fully verified locally) |
| Round | Target | Plan file | Commit | Verify | Cycles | Files | Notes |
|---|---|---|---|---|---|---|---|
| 0 | Assess + prioritize + plan | this ledger + spotify-recently-played-timestamp-2026-07-19.md | — | — | — | — | Seeded from latent-bugs-2026-07-19.md; 4 explore agents verified against current tree; most of snapshot already landed; new backlog built |
| 1 | Spotify played_at parse + bounded search retry | spotify-recently-played-timestamp-2026-07-19.md | 0315f65e | Host build gate + test-target compile GREEN; full test+coverage deferred to CI/VM | 1 | SpotifyAPI+Library.swift, SpotifyLibraryFetchTests.swift | 3 tests added incl. fractional fixture that would have failed pre-fix |
| 2 | Fresh bug-hunt (4 parallel explore agents) | this ledger | — | — | — | — | Beta-visible seed exhausted; surfaced 14 new targets (security/privacy on top): worker auth bypass, crash-report PII leak, consent gate, Weather null-decode, etc. |
| 2 | Worker Cf-Access auth-bypass fix | (from bug-hunt) | 3cb2c61e | npm test 74/74 GREEN (full gate, local) | 1 | feedback-worker/{src/index.js,test/dashboard-auth.test.js,README.md,.dev.vars.example} | Bypass closed by default; Access honored only when CF_ACCESS_ENABLED="true" |
| 3 | Crash-report redaction PII leak fix | (from bug-hunt) | abd2a5e7 | Build + test-target compile GREEN; full run on CI | 1 | CrashReportParser.swift, CrashReportParserTests.swift | Username no longer leaks (bare path + whitespace-bridge); /Users/Shared preserved; +3 tests |
| 4 | Crash harvester consent-watermark gate | (from bug-hunt) | d77e275d | Build + test-target compile GREEN; full run on CI | 1 | CrashReportHarvester.swift, CrashReportHarvesterTests.swift | Requires consent watermark AND opt-out toggle (mirrors heartbeat); +1 new test, 8 adapted |
| 5 | Weather null-element decode tolerance | (from bug-hunt) | 68a90028 | Build + test-target compile GREEN; full run on CI | 1 | WeatherManager.swift, WeatherModelTests.swift, WeatherManagerTests.swift | 5 Open-Meteo arrays accept null elements; +2 tests |
| PR | Checkpoint: push + full critical review of PR #147 | — | 0cdf382e | CI GREEN at 0cdf382e (macOS build-and-test 1997 tests + coverage, dockwitness, worker 74/74, Pages) | — | OptOutTransmissionTests.swift | Reviewed the full 20-file diff end-to-end. 1 blocker found + fixed: round 4's consent-watermark gate missed the release-checklist proofs in OptOutTransmissionTests — the consent-on control never established the watermark, producing exactly the 2 CI issues at fe370122. Fixed by establishing the watermark in both proofs (mirrors the round-4 adaptation). The green run also test-validates the three CI-deferred fixes abd2a5e7, d77e275d, 68a90028 at head. Non-blockers: redaction space-in-home-dir residual logged under Parked. Review was performed inline (single reviewer) rather than via parallel subagents — full diff read, every hunk. |
| 6 | FeedbackService flushPending re-entrancy | (from bug-hunt) | 803a9da6 | CI GREEN at 3ac586f1 (macOS build-and-test + coverage, dockwitness, worker, Pages) | 1 | FeedbackService.swift, FeedbackSender.swift, FeedbackServiceTests.swift | Overlapping drains collapse via in-flight flag; live-queue re-read each iteration; remove-by-value (cap-drop safe). New FeedbackSending seam + GatedFeedbackSender test double; +2 deterministic interleaving regression tests that fail pre-fix |
| 7 | Spotify queue: podcast episode blanks Up Next | (from bug-hunt) | 11a74967 | Logic-reviewed; full test run on CI at head | 1 | SpotifyAPI.swift, SpotifyAPI+Library.swift, SpotifyModelTests.swift, SpotifyLibraryTests.swift | SpotifyQueueItem discriminates on type; episodes render with show-as-artist; absent type stays strict-track. File overlap with round 1 (SpotifyAPI+Library.swift, disjoint hunks) — same Spotify theme, splits into the same theme PR |
| 8 | Calendar lookalike-URL meeting classification | (from bug-hunt) | 0c774471 | Logic-reviewed; full test run on CI at head | 1 | CalendarEventParsing.swift, CalendarEventParsingTests.swift | Host-anchored match (exact for meet/teams/facetime, suffix for zoom.us/webex.com), http(s)-only, case-insensitive; rejects path/query/userinfo/host-suffix tricks. Auto-open surface hardened |
| 9 | Lyrics LRC pair: stacked chorus tags + non-monotonic highlight freeze (#9+#10) | (from bug-hunt) | 97c1a16b | Logic-reviewed; full test run on CI at head | 1 | LyricsService.swift, LyricsParserTests.swift | One round for both (same 50-line kernel; round-1 precedent). Stacked tags expand + time-sort with post-sort unique ids (id contract: source-index → time-order, ids only ever opaque); currentLineID kernel extracted, _testCurrentIndex delegates instead of drifting. +3 tests |
| PR | Checkpoint #2 (after fix 10): full critical review of rounds 6–9 delta | — | (review) | CI GREEN at 3b89d61e (build-and-test 21:40Z; note the runs at 5b2e891 and 441a361 were CANCELLED by concurrency superseding, so 3b89d61e is the single authoritative macOS run for rounds 7–9) | — | git diff 0cdf382..3b89d61 | No blockers. Two deliberate contract adaptations, both logged (OptOut watermark at checkpoint 1; lyrics id order in round 9 — test renamed + strengthened with uniqueness assertion). Cross-file consumer sweep: lyric ids only ever opaque (fixture/integration tests construct their own); calendar consumers unchanged interface; no new silent fallbacks. PR body refreshed with full landed list. Review performed inline (single reviewer), full delta read |
| 10 | Worker heartbeat: uptime clamp + per-install event floor (#11) | (from bug-hunt) | eef33c74 | npm test 76/76 GREEN (full gate, local; was 74) | 1 | feedback-worker/{src/index.js,test/handlers.test.js} | Finite clamp to [0, 1y] (Infinity stored 0, was a failing bind); heartbeat_events append self-guards in SQL (NOT EXISTS newer-than-60s row per install, same batch — no extra round-trip); live upsert unconditional; app pings 120s so honest events never dropped |
| 11 | Worker safeDecode: malformed %-encoding fails closed (#12) | (from bug-hunt) | 92c10241 | npm test 77/77 GREEN (full gate, local; was 76) | 1 | feedback-worker/{src/index.js,test/dashboard-auth.test.js} | safeDecode (raw-string fallback) at all four decodeURIComponent sites; undecodable input flows to each path's own 302/404 instead of a pre-auth 500. File overlap with rounds 2/10 (src/index.js, disjoint hunks) — same worker theme |
| 12 | Calendar vanity-subdomain extraction (#13) | (from bug-hunt) | f8e0acc6 | Logic-reviewed; full test run on CI at head | 1 | CalendarEventParsing.swift, CalendarEventParsingTests.swift | Zoom/Webex subdomains allow hyphens + multiple levels; (?i) on all patterns; domain still anchored before the first path slash (lookalikes rejected, parity with round 8). File overlap with round 8 (same files, disjoint hunks) — same calendar theme |
| 13 | Worker /submit retry idempotency (#14) | (from bug-hunt) | 862c3bbf | npm test 79/79 GREEN (full gate, local; was 77) | 1 | feedback-worker/{src/index.js,test/handlers.test.js} | Dedupe probe (window+category+message+meta_json, 15 min) before writes; verbatim retry returns original id, writes no row/blob/email; fresh submittedAt in meta means new reports never collide; no schema change. File overlap with rounds 2/10/11 (worker theme) |
Generated by the harden-marathon tracker · impact data: .cursor/skills/harden-marathon/landed.json · ledger: .cursor/plans/harden-marathon-2026-07-19.md