Skip to content

feat(worker-bus): Phase 3 — Rust TaskSupervisor + text.analyze native task - #70

Merged
qnbs merged 5 commits into
mainfrom
feat/workerbus-v2-phase3-rust
Jun 3, 2026
Merged

feat(worker-bus): Phase 3 — Rust TaskSupervisor + text.analyze native task#70
qnbs merged 5 commits into
mainfrom
feat/workerbus-v2-phase3-rust

Conversation

@qnbs

@qnbsqnbs commented Jun 2, 2026

Copy link
Copy Markdown
Owner

WorkerBus v2 — Phase 3 (Rust TaskSupervisor) + build unblock + docs

Completes the native half of the hybrid router, unblocks the long-red Tauri build, and reconciles the docs.

Phase 3 — Rust TaskSupervisor

  • src-tauri/src/commands/task_supervisor.rs (new) + commands/mod.rs + lib.rs registration — storycraft_task_supervisor_ping (version) + storycraft_task_supervisor_submit (taskType dispatcher; unknown/bad payload → { success:false, error }, never a hard Err). First native task text.analyze (word/char/sentence/syllable + Flesch Reading Ease, pure Rust, 8 #[cfg(test)] tests).
  • services/rustTaskSupervisor.ts (new) — analyzeTextViaRust() probes isRustComputeAvailable() before routing (Rust-only task never hits the web pool; null → JS fallback); 5 unit tests.

Fixed — Tauri build (red since 2026-05-30, root-caused via CI dispatch)

  • src-tauri/Cargo.toml — removed unused, unresolvable specta = "2" / tauri-specta = "2" (only 2.0.0-rc.* exist → cargo resolution failed before any compile).
  • src-tauri/src/lora.rsLoraEnvReport deserialized but derived only Serialize (E0277) → added Deserialize.
  • task_supervisor.rsjson import → test mod; #[allow(dead_code)] on wire-contract fields.

Docs

CHANGELOG, AUDIT, ADR 0003 (WorkerBus hybrid routing), docs/TAURI-CI (native-verification method + known blockers), AGENTS, README doc-hub, ROADMAP, TODO.

Verification

  • ✅ Local: biome + tsc --noEmit + 5 TS tests green.
  • Native (the only Rust gate — no PR CI compiles src-tauri/):tauri-build.yml dispatched on this branch now compiles clean (Finished release ~4m18s) and bundles .deb / .rpm / .AppImage on ubuntu. The run's final updater-signing step fails on a malformed TAURI_SIGNING_PRIVATE_KEY repo secret (maintainer task), and the Windows runner fails in the setup composite (infra) — both unrelated to this code.

🤖 Generated with Claude Code

… task
Completes the WorkerBus v2 Rust half. The TS bridge (services/tauriTaskBridge.ts
+ hybridRouter.ts) already invokes storycraft_task_supervisor_{submit,ping}; this
adds the missing native commands.
- src-tauri/src/commands/task_supervisor.rs: ping (version) + submit dispatcher.
Unknown/bad-payload tasks resolve as { success:false, error } (never a hard Err),
matching the RustTaskResultEvent honest-failure convention so the router's
fallback is driven by result.success. First real task `text.analyze` computes
word/char/sentence/syllable counts + Flesch Reading Ease (pure Rust, no new deps).
8 #[cfg(test)] unit tests cover analysis + dispatch.
- commands/mod.rs + lib.rs: register both commands in generate_handler!.
- services/rustTaskSupervisor.ts: analyzeTextViaRust() probes isRustComputeAvailable()
before routing so a Rust-only task never hits the web worker pool; returns null
(JS fallback) when unavailable. 5 unit tests.
Verified locally: biome + tsc --noEmit + the 5 TS tests green. Rust cannot be
compiled on this low-end host (pre-existing specta="^2" resolution issue, unrelated
to this change) — gated by tauri-build.yml (workflow_dispatch), triggered on push.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codeant-ai

codeant-aiBot commented Jun 2, 2026

Copy link
Copy Markdown

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@vercel

vercelBot commented Jun 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
storycraft-studioReadyReadyPreview, CommentJun 2, 2026 11:53pm

@codeant-aicodeant-aiBot added the size:L This PR changes 100-499 lines, ignoring generated files label Jun 2, 2026

let outcome: Result<Value, String> = match request.task_type.as_str() {
"text.analyze" => run_text_analyze(&request.payload),
other => Err(format!("Unknown task type: {other}")),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Return a generic client-safe error message for unsupported task types and log the specific task type only in internal diagnostics. [custom_rule_security]

Severity Level: Critical 🚨

Why it matters? 🤔

The code returns a user-visible error string that includes the unsupported task type. That is a real security-style information leak from the external API surface, so the suggestion is correctly identifying a violation.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src-tauri/src/commands/task_supervisor.rs
**Line:** 86:86
**Comment:***Custom Rule Security: Return a generic client-safe error message for unsupported task types and log the specific task type only in internal diagnostics.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +111 to +114
let text = payload
.get("text")
.and_then(Value::as_str)
.ok_or_else(|| "text.analyze requires payload.text to be a string".to_string())?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Replace schema-specific validation text with a neutral user-facing error message, and keep payload shape details only in internal logs. [custom_rule_security]

Severity Level: Critical 🚨

Why it matters? 🤔

The validation error is returned to the caller and explicitly reveals the internal payload shape requirement (payload.text). This is a direct exposure of contract details, so the suggestion matches a real violation.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src-tauri/src/commands/task_supervisor.rs
**Line:** 111:114
**Comment:***Custom Rule Security: Replace schema-specific validation text with a neutral user-facing error message, and keep payload shape details only in internal logs.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment threadsrc-tauri/src/commands/task_supervisor.rs
`specta = "2"` / `tauri-specta = "2"` are referenced in no .rs file (build.rs only
calls tauri_build::build()) and are absent from Cargo.lock. The `"2"` (=^2 stable)
requirement matches nothing on crates.io — only `2.0.0-rc.*` prereleases exist — so
`cargo build` fails at resolution before compiling anything:
error: failed to select a version for the requirement `specta = "^2"`
This is why tauri-build.yml has been red since 2026-05-30 (the older lora.rs errors
were already past this gate). Removing the two dead deps is the root-cause fix:
resolution succeeds, the whole Rust crate (incl. the Phase 3 task_supervisor) can
build, and nothing functional is lost. TS-binding generation can re-add tauri-specta
later with a working exact rc pin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment on lines +41 to +45
const handle = await routeTask<RustTextAnalysis>(
'text.analyze',
{ text },
{ target: 'rust', rustComputeEnabled: true, priority: 'low' },
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: This call still goes through routeTask, which is designed to fall back to the web worker pool on Rust failure. That breaks the intended "Rust-only" routing guarantee and can enqueue text.analyze on web workers during transient Rust errors; call the Rust bridge directly (or add a strict no-fallback mode) for this path. [logic error]

Severity Level: Major ⚠️
- ⚠️ Rust text.analyze may run on web worker fallback.
- ⚠️ Rust-only compute flag cannot enforce native-only execution.
Steps of Reproduction ✅
1. Call `analyzeTextViaRust()` from `services/rustTaskSupervisor.ts:33-52` with a
non-empty string and `{ rustComputeEnabled: true }` so it proceeds past the early null
returns.
2. Inside `analyzeTextViaRust()`, `isRustComputeAvailable()` from
`services/tauriTaskBridge.ts:43-57` is awaited; assume it resolves `true`, then the helper
calls `routeTask<RustTextAnalysis>('text.analyze', {text}, {target: 'rust',
rustComputeEnabled: true, priority: 'low'})` at `services/rustTaskSupervisor.ts:41-45`.
3.`routeTask()` in `services/hybridRouter.ts:36-87` enters the Rust branch (lines 43-77),
calls `invokeRustTask(request)` at lines 48-56, and wraps it in a `try {...} catch (err)
{...}` block; if `invokeRustTask()` throws (for example, due to a transient Tauri error
or missing Rust command), the catch at lines 72-75 logs `'Rust route failed — falling back
to web worker pool'` and deliberately falls through to the web worker path.
4. The web worker path at `services/hybridRouter.ts:79-87` enqueues the same `taskType`
and payload via `bus.enqueue(taskType, payload, { ...busOpts, target: 'web' })`, returns a
`TaskHandle`, and `analyzeTextViaRust()` at `services/rustTaskSupervisor.ts:46-47` awaits
`handle.result`, so the supposedly Rust-only `'text.analyze'` task can be executed on the
WorkerBus web pool whenever the Rust invoke fails, contradicting the comment on lines
25-31 that this helper should never enqueue onto the web worker pool.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** services/rustTaskSupervisor.ts
**Line:** 41:45
**Comment:***Logic Error: This call still goes through `routeTask`, which is designed to fall back to the web worker pool on Rust failure. That breaks the intended "Rust-only" routing guarantee and can enqueue `text.analyze` on web workers during transient Rust errors; call the Rust bridge directly (or add a strict no-fallback mode) for this path.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

pub payload: Value,
pub priority: String,
pub target: String,
pub timeout_ms: u64,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The request includes timeout_ms, but task execution ignores it and runs handlers directly, so long-running or stuck tasks will never time out despite the contract exposing a timeout field. Enforce the timeout in the dispatcher so callers get bounded execution behavior. [incomplete implementation]

Severity Level: Major ⚠️
- ⚠️ Rust TaskSupervisor ignores timeoutMs for all tasks.
- ⚠️ Callers cannot bound execution time despite timeout field.
Steps of Reproduction ✅
1. A TypeScript caller invokes `routeTask()` from `services/hybridRouter.ts:36-87` with
options including `timeoutMs`, for example `routeTask('text.analyze', payload, {target:
'rust', rustComputeEnabled: true, timeoutMs: 1000})`.
2. In `routeTask()`, the Rust request envelope is built at
`services/hybridRouter.ts:48-55` as `const request: RustTaskRequest = {taskId, taskType,
payload, priority, target: 'rust', timeoutMs: opts.timeoutMs??300_000};`, so the
configured timeout is serialized into the `RustTaskRequest`.
3.`invokeRustTask()` in `services/tauriTaskBridge.ts:15-23` forwards this
`RustTaskRequest` over Tauri IPC to the Rust command `storycraft_task_supervisor_submit`,
which is registered in `src-tauri/src/lib.rs:52-61` and implemented in
`src-tauri/src/commands/task_supervisor.rs:80-107`.
4. Inside `storycraft_task_supervisor_submit`, the `RustTaskRequest` struct defined at
`src-tauri/src/commands/task_supervisor.rs:29-38` includes `pub timeout_ms: u64`, but the
function body only inspects `request.task_type` (lines 84-87) and never reads
`request.timeout_ms`; a repo-wide Grep for `timeout_ms` shows usage only in the struct
definition and test fixtures (lines 261, 278, 296), so task handlers (currently
`run_text_analyze`) always run to completion with no enforcement of the caller-provided
timeout.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src-tauri/src/commands/task_supervisor.rs
**Line:** 35:35
**Comment:***Incomplete Implementation: The request includes `timeout_ms`, but task execution ignores it and runs handlers directly, so long-running or stuck tasks will never time out despite the contract exposing a timeout field. Enforce the timeout in the dispatcher so callers get bounded execution behavior.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +126 to +130
let words: Vec<&str> = text.split_whitespace().filter(|w| !w.is_empty()).collect();
let word_count = words.len();

let sentence_count = count_sentences(text);
let syllable_count: usize = words.iter().map(|w| count_syllables(w)).sum();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Building a full Vec<&str> of all words before counting syllables adds avoidable memory pressure for large manuscripts. This task is explicitly for large inputs, so compute word and syllable counts in a streaming pass instead of collecting all tokens first. [performance]

Severity Level: Major ⚠️
- ⚠️ Text.analyze allocates Vec for every word token.
- ⚠️ Large manuscripts increase memory footprint unnecessarily.
Steps of Reproduction ✅
1. When a `text.analyze` task is submitted, `storycraft_task_supervisor_submit()` at
`src-tauri/src/commands/task_supervisor.rs:80-107` dispatches to `run_text_analyze()` for
`task_type == "text.analyze"` (lines 84-86), and `run_text_analyze()` at lines 110-118
extracts `payload["text"]` then calls `analyze_text(text)`.
2.`analyze_text()` in `src-tauri/src/commands/task_supervisor.rs:122-151` computes
character counts, then allocates `let words: Vec<&str>=text.split_whitespace().filter(|w|!w.is_empty()).collect();`atline126andderives`letword_count=words.len();`atline127,storingaborrowedsliceforeverytoken.3.Thefunctionthenseparatelycomputes`letsentence_count=count_sentences(text);`atline129,whichre-iteratesovertheentire`text`,and`letsyllable_count:usize=words.iter().map(|w|count_syllables(w)).sum();`atline130,traversingthe`words`vectortocountsyllablespertoken.4.Forlargemanuscripts(themodulecommentatlines7-9explicitlycallsoutoffloading"large manuscripts"),eachcallto`analyze_text()`materializesa`Vec`ofallwhitespace-separatedtokenseventhoughonlyaggregatecountsareused;thisper-wordallocationincreasesmemoryandallocationoverheadonevery`text.analyze`task,whereasastreamingpassover`split_whitespace()`couldcomputeboth`word_count`and`syllable_count`withoutstoringthefulllist,matchingthe"no allocation beyond theword iterator"intentstatedatlines120-121.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src-tauri/src/commands/task_supervisor.rs
**Line:** 126:130
**Comment:***Performance: Building a full `Vec<&str>` of all words before counting syllables adds avoidable memory pressure for large manuscripts. This task is explicitly for large inputs, so compute word and syllable counts in a streaming pass instead of collecting all tokens first.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codeant-ai

codeant-aiBot commented Jun 2, 2026

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

qnbsand others added 3 commits June 3, 2026 01:34
…port
Two fixes surfaced once the specta resolution block was removed and the Rust crate
actually compiled on CI:
- lora.rs: `check_lora_environment` deserializes the Python sidecar's JSON stdout via
`serde_json::from_str::<LoraEnvReport>` (lora.rs:209), but the struct derived only
Serialize -> E0277 broke the whole-crate compile (the real reason tauri-build.yml
has been red since 2026-05-30). Add Deserialize (already imported).
- task_supervisor.rs: `json` is only used in #[cfg(test)] -> move the import into the
test module to silence the unused-import warning in the non-test build.
Verified: this is the last error/warning the ubuntu+macos runners reported after the
specta fix; re-dispatching tauri-build to confirm a clean native compile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t fields
The native ubuntu/macos build now compiles cleanly and produces .deb/.rpm/.AppImage
bundles (verified via tauri-build dispatch). The only remaining diagnostic was a
dead_code warning: priority/target/timeoutMs/retryPolicy are accepted from the TS
router per the worker-bus contract but not yet read by the dispatcher. allow(dead_code)
with a note until the retry/timeout path is implemented.
NOTE: tauri-build still exits non-zero at the very end on the Tauri updater signing
step ("incorrect updater private key password: Missing comment in secret key") — a
malformed repo signing secret, unrelated to code; the app + all 3 bundles build fine.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d findings
Bring the living/canonical docs in line with the current code + CI reality. Sprint
handoffs, docs/history, and version-sprint docs are archival (point-in-time) and left
frozen by design.
- CHANGELOG.md: 2026-06-03 cluster — WorkerBus v2 Phase 3 (Rust TaskSupervisor + text.analyze),
Local-AI Phase 2.3 perf sprint (pipelineLruCache dispose-on-evict, aiRetry property tests,
kokoro, ADRs, coverage ratchet L74/B60/F66/S72), and the Tauri-build unblock (### Fixed).
- AUDIT.md: new 2026-06-03 audit section (Phase 3 + the 3 root-caused tauri-build blockers +
the remaining non-code updater-signing / Windows-setup issues); follow-up chain extended.
- docs/adr/0003-workerbus-hybrid-routing.md (new) + ADR index: records the v2 runtime →
manager → hybrid router → legacy adapter → Rust TaskSupervisor layering, the honest-failure
contract, and the "no PR-CI gate for Rust" verification constraint.
- docs/TAURI-CI.md: "Verifying native (Rust) changes" (dispatch-on-branch is the gate) +
"Build health & known blockers (2026-06-03)".
- AGENTS.md: agent rule — no PR-CI gate for src-tauri; dispatch tauri-build on the branch.
- README.md: ADR doc-hub entry now lists WorkerBus hybrid routing.
- ROADMAP.md: Local-AI Perfection Phases 2.3/2.4 marked done; WorkerBus Phase 3 noted.
No i18n keys added (docs only); badges left as-is (match last CI-measured numbers).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qnbs
qnbs merged commit eff15c1 into mainJun 3, 2026
16 checks passed
@qnbs
qnbs deleted the feat/workerbus-v2-phase3-rust branch June 3, 2026 00:11
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:LThis PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@qnbs