fix: resolve three code-review-filed bugs (#502, #504, #507) - #517
Draft
TYRMars wants to merge 2 commits into
Draft
fix: resolve three code-review-filed bugs (#502, #504, #507)#517TYRMars wants to merge 2 commits into
TYRMars wants to merge 2 commits into
Conversation
…omicWrite concurrent-safe Resolves two independent, deterministic bugs surfaced by the code-review task. #504 — recomputeNextRun re-anchored with previousRun=undefined, which for a `once` schedule discards the only "already fired" signal. PATCHing any field of a completed one-shot (a UI save echoing the unchanged schedule back) wrote the past run_at into next_run_at and re-executed the prompt. Thread the task's own last_run_at through instead: interval re-anchoring for a never-run task is unchanged, and a finished `once` stays finished. #502 — atomicWrite derived its staging file solely from the target path, so concurrent writers of one id shared a single `.tmp`. The rename race dropped the loser's write with ENOENT (silently swallowed by the REST message routes). Use a per-write staging name (`<path>.<pid>.<uuid>.tmp`) so the outcome is plain last-writer-wins, and clean up the staging file on failure. All list() scanners gate on `.json`, so the new tmp names remain invisible to them. Benefits every JSON-file store plus @jarvis/observability and @jarvis/learning. Adds regression tests for both. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TpB8YUSXH3RpNwYBhsHxkC
Issue #507: the store-side Fanout has zero subscribers in the running server, so todo_upserted / requirement_upserted frames are never sent — yet the store doc comments, the SPA frame handlers, and their unit tests all read as though the bridge is live, so the next reader takes a tested-but-dead path at face value. Actually wiring the /v1/chat/ws bridge (subscribe to state.todos + the project/requirement fanout, forward workspace-filtered frames, unsubscribe on socket close) spans multiple stores and must match the SPA's exact frame-decode contracts — a design-level change that needs a maintainer's call on which frames/filtering to ship, not an autonomous guess. Emitting subtly-wrong frames would be worse than a documented gap. So, per the issue's own alternative resolution, align the docs to reality without changing runtime behaviour: - packages/todo/src/store.ts — the fanout contract is a ready seam, not a live feature; subscribe() has no non-test caller. - packages/server/src/todos-routes.ts — strengthen the "when wired" hedge into an explicit not-wired-yet note. - apps/jarvis-web/src/services/{todos,requirements}.ts — the frame handlers exercise the contract, not a live path; no live cross-tab updates today. No code paths change; the tested-but-dead handlers now read honestly and the full bridge is flagged as a deferred, maintainer-owned task. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TpB8YUSXH3RpNwYBhsHxkC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves three independent bugs filed by the scheduled
crcode-review task. Two are behavioural fixes with regression tests; the third is a documentation-alignment resolution for a deferred, design-level gap (see the caveat).Closes #504 — completed
onceautomations get re-armed on PATCHrecomputeNextRunre-anchored the schedule withpreviousRun = undefined. For anintervalschedule that is correct, but for aonceschedulepreviousRun(last_run_at) is the only "already fired" signal — passingundefinedwrote the original, now-pastrun_atback intonext_run_at, making a finished one-shot immediately due again.The single caller fires on any
PATCH /v1/automations/:idcarrying aschedulekey — including a UI save that echoes the unchanged schedule back — so editing an unrelated field (rename, model change) silently re-executed the task's prompt (backup/deploy/send report) that the user scheduled to run exactly once.Fix: thread the task's own
last_run_atthrough (scheduleNextAfter(task.schedule, task.last_run_at, now)). Interval re-anchoring for a never-run task is unchanged; a completedoncestays completed.Closes #502 —
atomicWriteshared.tmploses concurrent writesatomicWritederived its staging file solely from the target path, so all concurrent writers of one id shared a single<path>.tmp. Writer B rewrote A's staging file; whoever renamed first consumed it, and the loser'srenamehitENOENT— a silently-dropped write (the REST message routes catch-and-ignore the failure).Fix: per-write staging name (
<path>.<pid>.<uuid>.tmp) so the outcome is plain last-writer-wins, and remove the staging file on failure so a crashed write leaves no litter. Everylist()scanner gates onendsWith(".json"), so the new tmp names stay invisible to directory scans. This helper backs every JSON-file store plus@jarvis/observabilityand@jarvis/learning.Addresses #507 — store→WS fanout has zero subscribers (docs-alignment; wiring deferred)
The store-side
Fanouthas no subscriber in the running server, sotodo_upserted/requirement_upsertedframes are never emitted — yet the store doc comments, the SPA frame handlers, and their unit tests all read as though the bridge is live.Actually wiring the
/v1/chat/wsbridge (subscribe tostate.todos+ the project/requirement fanout, forward workspace-filtered frames, unsubscribe on close) spans multiple stores and must match the SPA's exact frame-decode contracts — a design-level change a maintainer should sign off on (which frames, which filtering semantics). Emitting subtly-wrong frames would be worse than a documented gap, so this run takes the issue's own alternative resolution: align the docs to reality so the tested-but-dead handlers stop reading as wired, and flag the full bridge as a deferred, maintainer-owned task. No runtime behaviour changes.packages/todo/src/store.ts,packages/server/src/todos-routes.ts— mark the fanout a ready seam, not a live feature.apps/jarvis-web/src/services/{todos,requirements}.ts— the frame handlers exercise the contract, not a live path.Tests
recomputeNextRun keeps a completed 'once' task completed (#504)and an interval re-anchor pin.atomicWrite: concurrent writes to one target don't ENOENT — last-writer-wins— three overlapping writes all settle, the survivor is exactly one writer's full payload, no.tmplitter remains.@jarvis/automation(38/38), the json-file portion of@jarvis/store(20/20), and@jarvis/todo+@jarvis/servertypecheck are green; eslint clean. CIbuildpassed. The sqlite test failures seen locally are an uncompiledbetter-sqlite3native binding (build scripts skipped in this environment), unrelated to this change.🤖 Generated with Claude Code
https://claude.ai/code/session_01TpB8YUSXH3RpNwYBhsHxkC